├── server ├── src │ ├── app │ │ ├── public │ │ │ └── README.md │ │ ├── io │ │ │ ├── middleware │ │ │ │ ├── leave.ts │ │ │ │ ├── auth.ts │ │ │ │ └── join.ts │ │ │ └── controller │ │ │ │ └── nsp.ts │ │ ├── extend │ │ │ ├── helper.ts │ │ │ └── context.ts │ │ ├── helper │ │ │ ├── parseMsg.ts │ │ │ └── logTransport.ts │ │ ├── middleware │ │ │ ├── notFound.ts │ │ │ └── elkLogger.ts │ │ ├── router.ts │ │ ├── controller │ │ │ ├── foo.js │ │ │ ├── user.ts │ │ │ ├── room.ts │ │ │ ├── account.ts │ │ │ └── gameRecord.ts │ │ └── core │ │ │ ├── Poker.ts │ │ │ ├── Player.ts │ │ │ └── PokerStyle.ts │ ├── interface │ │ ├── ILoginResult.ts │ │ ├── ITickMsg.ts │ │ ├── IRequestBody.ts │ │ ├── IAccountInfo.ts │ │ ├── IUser.ts │ │ ├── IResult.ts │ │ ├── service │ │ │ ├── IUserService.ts │ │ │ └── IAccountService.ts │ │ ├── IFetchOptions.ts │ │ ├── IGame.ts │ │ ├── IPlayer.ts │ │ ├── IRoom.ts │ │ ├── ICommandRecord.ts │ │ ├── IGameRoom.ts │ │ └── Ilog.ts │ ├── config │ │ ├── config.test.ts │ │ ├── plugin.ts │ │ └── config.default.ts │ ├── app.ts │ ├── service │ │ ├── player.ts │ │ ├── user.ts │ │ ├── game.ts │ │ ├── room.ts │ │ ├── commandRecord.ts │ │ └── account.ts │ ├── lib │ │ ├── baseController.ts │ │ ├── baseService.ts │ │ └── baseSocketController.ts │ └── utils │ │ └── Link.ts ├── .idea │ ├── .gitignore │ ├── codeStyles │ │ ├── codeStyleConfig.xml │ │ └── Project.xml │ ├── misc.xml │ ├── vcs.xml │ ├── modules.xml │ ├── inspectionProfiles │ │ └── Project_Default.xml │ └── server.iml ├── tslint.json ├── .travis.yml ├── appveyor.yml ├── tsconfig.json ├── test │ ├── utils │ │ └── link.test.ts │ └── app │ │ └── core │ │ ├── pokerStyle.test.ts │ │ └── pokerGame.test.ts ├── package.json └── README.md ├── client ├── .idea │ ├── .gitignore │ ├── codeStyles │ │ ├── codeStyleConfig.xml │ │ └── Project.xml │ ├── misc.xml │ ├── vcs.xml │ ├── jsLinters │ │ └── tslint.xml │ ├── modules.xml │ ├── inspectionProfiles │ │ └── Project_Default.xml │ └── client.iml ├── src │ ├── interface │ │ ├── IMsgList.ts │ │ ├── IRoom.ts │ │ ├── ISit.ts │ │ ├── IUser.ts │ │ ├── IGameRecord.ts │ │ └── IPlayer.ts │ ├── assets │ │ ├── bg.png │ │ ├── logo.png │ │ ├── poke.png │ │ ├── mp3 │ │ │ ├── click.mp3 │ │ │ ├── fold.mp3 │ │ │ ├── raise.mp3 │ │ │ └── income.mp3 │ │ ├── poke-icon.png │ │ ├── gold.svg │ │ └── less │ │ │ └── base.less │ ├── shims-vue.d.ts │ ├── .editorconfig │ ├── store │ │ └── index.ts │ ├── utils │ │ ├── origin.ts │ │ ├── init.ts │ │ ├── map.ts │ │ ├── request.ts │ │ ├── Link.ts │ │ └── PokerStyle.ts │ ├── plugin.d.ts │ ├── shims-tsx.d.ts │ ├── components │ │ ├── Emoji.vue │ │ ├── msgList.vue │ │ ├── Audio.vue │ │ ├── Notice.vue │ │ ├── CardStyle.vue │ │ ├── Toast.vue │ │ ├── SendMsg.vue │ │ ├── Record.vue │ │ ├── BuyIn.vue │ │ ├── Range.vue │ │ ├── XInput.vue │ │ ├── CommonCard.vue │ │ ├── Player.vue │ │ ├── CardList.vue │ │ ├── GameRecord.vue │ │ └── Action.vue │ ├── main.ts │ ├── service │ │ └── index.ts │ ├── router │ │ └── index.ts │ ├── App.vue │ ├── plugins │ │ └── toast.ts │ └── views │ │ ├── login.vue │ │ ├── register.vue │ │ └── home.vue ├── babel.config.js ├── public │ ├── favicon.ico │ └── index.html ├── appveyor.yml ├── tslint.json ├── README.md ├── tsconfig.json └── package.json ├── gif ├── qr.jpg ├── demo1.gif ├── demo2.gif ├── demo3.gif └── demo4.gif ├── yarn.lock ├── .travis.yml ├── .gitignore ├── SECURITY.md ├── README.md └── database └── poker.sql /server/src/app/public/README.md: -------------------------------------------------------------------------------- 1 | ## public static file directory! -------------------------------------------------------------------------------- /client/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /server/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /gif/qr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/gif/qr.jpg -------------------------------------------------------------------------------- /gif/demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/gif/demo1.gif -------------------------------------------------------------------------------- /gif/demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/gif/demo2.gif -------------------------------------------------------------------------------- /gif/demo3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/gif/demo3.gif -------------------------------------------------------------------------------- /gif/demo4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/gif/demo4.gif -------------------------------------------------------------------------------- /client/src/interface/IMsgList.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IMsgList { 3 | msg: string; 4 | } 5 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /server/src/interface/ILoginResult.ts: -------------------------------------------------------------------------------- 1 | export interface ILoginResult { 2 | token: string; 3 | } 4 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/src/assets/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/client/src/assets/bg.png -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/assets/poke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/client/src/assets/poke.png -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /client/src/assets/mp3/click.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/client/src/assets/mp3/click.mp3 -------------------------------------------------------------------------------- /client/src/assets/mp3/fold.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/client/src/assets/mp3/fold.mp3 -------------------------------------------------------------------------------- /client/src/assets/mp3/raise.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/client/src/assets/mp3/raise.mp3 -------------------------------------------------------------------------------- /client/src/assets/poke-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/client/src/assets/poke-icon.png -------------------------------------------------------------------------------- /server/src/interface/ITickMsg.ts: -------------------------------------------------------------------------------- 1 | export interface ITickMsg { 2 | type: string; 3 | message: string; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/assets/mp3/income.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzdwc/TexasPokerGame/HEAD/client/src/assets/mp3/income.mp3 -------------------------------------------------------------------------------- /client/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue'; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /server/src/interface/IRequestBody.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IRequestBody { 3 | head: any; 4 | body: any; 5 | } 6 | -------------------------------------------------------------------------------- /server/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-egg"], 3 | "rules": { 4 | "linebreak-style": [ true, "CRLF" ] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /server/src/interface/IAccountInfo.ts: -------------------------------------------------------------------------------- 1 | export interface IAccountInfo { 2 | userAccount: string; 3 | password: string; 4 | nickName?: string; 5 | } 6 | -------------------------------------------------------------------------------- /server/src/interface/IUser.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | nickName: string; 3 | account: string; 4 | password?: string; 5 | id?: number; 6 | } 7 | -------------------------------------------------------------------------------- /client/src/interface/IRoom.ts: -------------------------------------------------------------------------------- 1 | export interface IRoom { 2 | roomNumber?: string; 3 | isShort: boolean; 4 | time?: number; 5 | smallBlind: number; 6 | } 7 | -------------------------------------------------------------------------------- /client/src/interface/ISit.ts: -------------------------------------------------------------------------------- 1 | import { IPlayer } from '@/interface/IPlayer'; 2 | 3 | export default interface ISit { 4 | player: IPlayer | null; 5 | position: number; 6 | } 7 | -------------------------------------------------------------------------------- /client/src/interface/IUser.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | nickName: string; 3 | counter: number; 4 | userId?: number; 5 | buyIn: number; 6 | account: string; 7 | } 8 | -------------------------------------------------------------------------------- /client/.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /server/.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /server/src/app/io/middleware/leave.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'midway'; 2 | 3 | export default function leave(): any { 4 | return async (ctx: Context, next: () => Promise) => { 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /client/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /server/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /server/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/src/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /client/.idea/jsLinters/tslint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '10' 5 | before_install: 6 | - npm i npminstall -g 7 | install: 8 | - npminstall 9 | script: 10 | - npm run ci 11 | after_script: 12 | - npminstall codecov && codecov 13 | -------------------------------------------------------------------------------- /server/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '10' 5 | before_install: 6 | - npm i npminstall -g 7 | install: 8 | - npminstall 9 | script: 10 | - npm run ci 11 | after_script: 12 | - npminstall codecov && codecov 13 | -------------------------------------------------------------------------------- /server/src/interface/IResult.ts: -------------------------------------------------------------------------------- 1 | export enum ResultCode { 2 | SUCCESS = '000000', 3 | FAIL = '100000', 4 | ONT_AUTH = '999999', 5 | } 6 | 7 | export interface IResult { 8 | code: ResultCode; 9 | data: any; 10 | message: string; 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | test/unit/coverage 7 | run/ 8 | logs/ 9 | npm-debug.* 10 | /server/src/config/config.local.ts 11 | /server/src/config/config.prod.ts 12 | /client/.idea 13 | /server/.idea 14 | /.idea -------------------------------------------------------------------------------- /client/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | Vue.use(Vuex); 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | }, 9 | mutations: { 10 | }, 11 | actions: { 12 | }, 13 | modules: { 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /server/src/config/config.test.ts: -------------------------------------------------------------------------------- 1 | import { EggAppConfig, PowerPartial } from 'egg'; 2 | 3 | export default () => { 4 | const config: PowerPartial = {}; 5 | // business domain 6 | config.apiDomain = { 7 | }; 8 | return config; 9 | }; 10 | -------------------------------------------------------------------------------- /client/src/utils/origin.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | url: process.env.NODE_ENV !== 'production' ? 3 | process.env.NODE_ENV === 'develop' ? 'http://172.22.72.70:7001' 4 | : 'http://127.0.0.1:7001' : 'http://www.jojgame.com:7001', 5 | res: location.href.split('#')[0] + '#', 6 | }; 7 | -------------------------------------------------------------------------------- /client/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /server/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /server/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/.idea/client.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /server/.idea/server.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '10' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /server/appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '10' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /server/src/interface/service/IUserService.ts: -------------------------------------------------------------------------------- 1 | import { IAccountInfo } from '../IAccountInfo'; 2 | import { IUser } from '../IUser'; 3 | 4 | export interface IUserService { 5 | findById(uid: string): Promise; 6 | findByAccount(account: string): Promise; 7 | addUser(accountInfo: IAccountInfo): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /client/src/plugin.d.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { ToastExtendConstructor, IOptions } from '../src/plugins/toast'; 3 | 4 | interface IPlugin { 5 | toast(options: string | IOptions): ToastExtendConstructor; 6 | } 7 | 8 | declare module 'vue/types/vue' { 9 | interface Vue { 10 | $plugin: IPlugin; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /server/src/app/extend/helper.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parseMsg(action: any, payload = {}, metadata = {}) { 3 | const meta = Object.assign({}, { 4 | timestamp: Date.now(), 5 | }, metadata); 6 | 7 | return { 8 | meta, 9 | data: { 10 | action, 11 | payload, 12 | }, 13 | }; 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /server/src/app/helper/parseMsg.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parseMsg(action: any, payload = {}, metadata = {}) { 3 | const meta = Object.assign({}, { 4 | timestamp: Date.now(), 5 | }, metadata); 6 | 7 | return { 8 | meta, 9 | data: { 10 | action, 11 | payload, 12 | }, 13 | }; 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /client/src/interface/IGameRecord.ts: -------------------------------------------------------------------------------- 1 | export interface IGameRecord { 2 | gameId: number; 3 | } 4 | 5 | export interface ICommandRecord { 6 | roomNumber: string; 7 | gameId: number; 8 | userId: string; 9 | type: string; 10 | pot: number; 11 | commonCard: string; 12 | handCard?: string; 13 | gameStatus: number; 14 | command: string; 15 | counter: number; 16 | } 17 | -------------------------------------------------------------------------------- /client/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /server/src/interface/IFetchOptions.ts: -------------------------------------------------------------------------------- 1 | import { HttpMethod } from 'urllib'; 2 | 3 | /** 4 | * @description User-Service abstractions 5 | */ 6 | export interface IFetchOptions { 7 | method?: string; 8 | url: string; 9 | body: object; 10 | head?: object; 11 | timeout?: number; 12 | headers?: object; 13 | type?: HttpMethod; 14 | ssjToken: string; 15 | } 16 | -------------------------------------------------------------------------------- /client/src/interface/IPlayer.ts: -------------------------------------------------------------------------------- 1 | enum PlayerType { 2 | READY, 3 | SIT_DOWN, 4 | GAMING, 5 | } 6 | 7 | export interface IPlayer { 8 | counter: number; 9 | nickName: string; 10 | actionSize: number; 11 | actionCommand: string; 12 | type: string; 13 | userId?: string; 14 | handCard?: string[]; 15 | buyIn: number; 16 | status: number; 17 | income?: number; 18 | isSit: boolean; 19 | delayCount: number; 20 | } 21 | -------------------------------------------------------------------------------- /client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "extends": ["tslint:recommended"], 4 | "linterOptions": { 5 | "exclude": ["node_modules/**"] 6 | }, 7 | "rules": { 8 | "indent": [true, "spaces", 2], 9 | "interface-name": false, 10 | "no-consecutive-blank-lines": false, 11 | "object-literal-sort-keys": false, 12 | "ordered-imports": false, 13 | "no-console": false, 14 | "quotemark": [true, "single"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server/src/interface/IGame.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IGame { 3 | id?: number; 4 | roomNumber?: string; 5 | pot: number; 6 | status: number; 7 | commonCard: string; 8 | winners?: string; 9 | } 10 | 11 | export interface IGameService { 12 | findByID(gid: number): Promise; 13 | findByIDs(ids: number[]): Promise; 14 | findByRoomNumber(roomNumber: number): Promise; 15 | add(game: IGame): Promise; 16 | update(game: IGame): Promise; 17 | } 18 | -------------------------------------------------------------------------------- /client/src/utils/init.ts: -------------------------------------------------------------------------------- 1 | let setRem = () => { 2 | let curWidth = document.documentElement.clientWidth || window.screen.width; 3 | const basicWidth = 375; 4 | const basicFontSize = 20; 5 | let calcFontSize = 0; 6 | curWidth = curWidth > 640 ? 640 : curWidth < 320 ? 320 : curWidth; 7 | calcFontSize = (curWidth / basicWidth) * basicFontSize; 8 | document.documentElement.style.fontSize = calcFontSize + 'px'; 9 | }; 10 | setRem(); 11 | window.addEventListener('resize', () => { 12 | setRem(); 13 | }); 14 | -------------------------------------------------------------------------------- /server/src/interface/IPlayer.ts: -------------------------------------------------------------------------------- 1 | export interface IPlayerDTO { 2 | roomNumber: number; 3 | userId: string; 4 | counter: number; 5 | gameId: number; 6 | handCard: string; 7 | buyIn: number; 8 | } 9 | 10 | export interface UpdatePlayerDTO { 11 | userId: string; 12 | counter: number; 13 | gameId: number; 14 | playerId: number; 15 | } 16 | 17 | export interface IPlayerService { 18 | findByRoomNumber(roomNumber: number): Promise; 19 | 20 | add(gameRecord: IPlayerDTO): Promise; 21 | } 22 | -------------------------------------------------------------------------------- /server/src/config/plugin.ts: -------------------------------------------------------------------------------- 1 | import { EggPlugin } from 'egg'; 2 | 3 | const plugin: EggPlugin = { 4 | static: true, 5 | cors: { 6 | enable: true, 7 | package: 'egg-cors', 8 | }, 9 | redis: { 10 | enable: true, 11 | package: 'egg-redis', 12 | }, 13 | io: { 14 | enable: true, 15 | package: 'egg-socket.io', 16 | }, 17 | jwt: { 18 | enable: true, 19 | package: 'egg-jwt', 20 | }, 21 | mysql: { 22 | enable: true, 23 | package: 'egg-mysql', 24 | }, 25 | }; 26 | 27 | export default plugin; 28 | -------------------------------------------------------------------------------- /server/src/interface/IRoom.ts: -------------------------------------------------------------------------------- 1 | export interface IRoom { 2 | roomNumber?: string; 3 | isShort: boolean; 4 | time?: number; 5 | smallBlind: number; 6 | } 7 | 8 | export interface IRoomService { 9 | findById(uid: string): Promise; 10 | findRoomNumber(roomNumber: string): Promise; 11 | findByRoomNumber(roomNumber: string): Promise; 12 | add(isShort: boolean, smallBlind: number, time?: number): Promise; 13 | // join(roomNumber: string, userName: string): void; 14 | // leave(roomNumber: string, message: string): void; 15 | } 16 | -------------------------------------------------------------------------------- /server/src/app/middleware/notFound.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | 3 | /** 4 | * 404 5 | * @returns {any} 6 | */ 7 | export default function notFound(): any { 8 | return async (ctx: Context, next: () => Promise) => { 9 | await next(); 10 | if (ctx.status === 404 && !ctx.body) { 11 | if (ctx.acceptJSON) { 12 | ctx.body = { 13 | code: '404', 14 | data: {}, 15 | msg: 'page not found', 16 | }; 17 | } else { 18 | ctx.body = 'Page Not Found'; 19 | } 20 | } 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /client/src/components/Emoji.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 25 | -------------------------------------------------------------------------------- /server/src/interface/ICommandRecord.ts: -------------------------------------------------------------------------------- 1 | export interface ICommandRecord { 2 | roomNumber: string; 3 | gameId: number; 4 | userId: string; 5 | type: string; 6 | pot: number; 7 | commonCard: string; 8 | handCard?: string; 9 | gameStatus: number; 10 | command: string; 11 | counter: number; 12 | } 13 | 14 | export interface ICommandRecordService { 15 | findByGameID(gameID: number): Promise; 16 | findByGameIDs(gameIDs: number[]): Promise; 17 | add(commandRecord: ICommandRecord): Promise; 18 | findPast7DayGameIDsByUserID(userID: number): Promise; 19 | } 20 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | poke-game-front-ts 2 | ===================== 3 | ## Setup 4 | 5 | ``` 6 | yarn 7 | yarn serve 8 | ``` 9 | ### Compiles and minifies for production 10 | 11 | ``` 12 | yarn run build 13 | ``` 14 | 15 | ### Run your tests 16 | 17 | ``` 18 | yarn run test 19 | ``` 20 | 21 | ## Project structure 22 | ``` 23 | ├─public // html 24 | └─src 25 | ├─assets 26 | │ ├─less 27 | │ └─mp3 28 | ├─components 29 | ├─interface 30 | ├─plugins 31 | ├─router 32 | ├─service // api 33 | ├─store 34 | ├─utils 35 | └─views 36 | 37 | ``` 38 | ## License 39 | The MIT License (MIT) 40 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /client/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | import store from './store'; 5 | import VConsole from 'vconsole'; 6 | import './utils/init'; 7 | import toastPlugin from './plugins/toast'; 8 | // 快速点击bug 9 | // import fastClick from 'fastclick'; 10 | Vue.use(toastPlugin); 11 | 12 | Vue.config.productionTip = false; 13 | 14 | if (process.env.NODE_ENV !== 'production') { 15 | // tslint:disable-next-line:no-unused-expression 16 | new VConsole(); 17 | } 18 | // @ts-ignore 19 | // fastClick.attach(document.body); 20 | 21 | 22 | new Vue({ 23 | router, 24 | store, 25 | render: (h) => h(App), 26 | }).$mount('#app'); 27 | -------------------------------------------------------------------------------- /client/src/utils/map.ts: -------------------------------------------------------------------------------- 1 | export default (cards: string []) => { 2 | const cardNumber = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A']; 3 | const color = ['♦', '♣', '♥', '♠']; 4 | return cards?.map((c: string) => { 5 | const cNumber = c.charCodeAt(0) - 97; 6 | const cColor = Number(c[1]) - 1; 7 | return [`${cardNumber[cNumber]}`, `${color[cColor]}`]; 8 | }); 9 | }; 10 | 11 | const mapCard = (card: string) => { 12 | const cardNumber = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A']; 13 | const color = ['d', 'c', 'h', 's']; 14 | const cNumber = card.charCodeAt(0) - 97; 15 | const cColor = Number(card[1]) - 1; 16 | return [`${cardNumber[cNumber]}`, `${color[cColor]}`]; 17 | }; 18 | export { mapCard }; 19 | -------------------------------------------------------------------------------- /server/src/interface/IGameRoom.ts: -------------------------------------------------------------------------------- 1 | import { PokerGame } from '../app/core/PokerGame'; 2 | import { IPlayer } from '../app/core/Player'; 3 | import { ILinkNode } from '../utils/Link'; 4 | 5 | export interface IGameRoom { 6 | number: string; 7 | roomInfo: IRoomInfo; 8 | } 9 | 10 | export interface ISit { 11 | player: IPlayer; 12 | position: number; 13 | } 14 | 15 | export interface IRoomConfig { 16 | isShort: boolean; 17 | smallBlind: number; 18 | time?: number; 19 | } 20 | 21 | export interface IRoomInfo { 22 | players: IPlayer[]; 23 | sit: ISit[]; 24 | game: PokerGame | null; 25 | sitLink: ILinkNode | null; 26 | gameId?: number; 27 | config: IRoomConfig; 28 | } 29 | -------------------------------------------------------------------------------- /server/src/app/router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'midway'; 2 | 3 | export default function (app: Application) { 4 | app.io.of('/socket').route('exchange', app.io.controller.nsp.exchange); 5 | app.io.of('/socket').route('broadcast', app.io.controller.nsp.broadcast); 6 | app.io.of('/socket').route('buyIn', app.io.controller.game.buyIn); 7 | app.io.of('/socket').route('playGame', app.io.controller.game.playGame); 8 | app.io.of('/socket').route('action', app.io.controller.game.action); 9 | app.io.of('/socket').route('sitDown', app.io.controller.game.sitDown); 10 | app.io.of('/socket').route('standUp', app.io.controller.game.standUp); 11 | app.io.of('/socket').route('delayTime', app.io.controller.game.delayTime); 12 | } 13 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": ["webpack-env"], 15 | "paths": { 16 | "@/*": ["src/*"] 17 | }, 18 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 19 | }, 20 | "include": [ 21 | "src/**/*.ts", 22 | "src/**/*.tsx", 23 | "src/**/*.vue", 24 | "tests/**/*.ts", 25 | "tests/**/*.tsx" 26 | ], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /server/src/app/controller/foo.js: -------------------------------------------------------------------------------- 1 | 2 | function convertBase( numberString, fromBase, toBase ) { 3 | let numberToTen = toTen(numberString, fromBase) 4 | let str = '' 5 | while (numberToTen) { 6 | let toNumber = numberToTen % toBase.base 7 | str += toBase.map[toNumber] 8 | numberToTen = Math.floor(numberToTen / toBase.base) 9 | } 10 | return str 11 | } 12 | 13 | function toTen(numberString, fromBase) { 14 | let baseMap = fromBase.map.split('') 15 | let numberArr = numberString.split('') 16 | let sum = 0 17 | for (let i = 0; i <= numberArr.length - 1; i++) { 18 | let currNumber = numberArr[i] 19 | let baseIndex = baseMap.findIndex(b => b === currNumber) 20 | sum += ((baseIndex + 1) * fromBase.base ** (numberArr.length - 1 - i)) 21 | } 22 | return sum 23 | } 24 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= htmlWebpackPlugin.options.title %> 10 | 11 | 12 | 13 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue. 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /server/src/app/controller/user.ts: -------------------------------------------------------------------------------- 1 | import { Context, inject, controller, post, provide, plugin } from 'midway'; 2 | import BaseController from '../../lib/baseController'; 3 | import { IUserService } from '../../interface/service/IUserService'; 4 | 5 | @provide() 6 | @controller('/node/user') 7 | export class UserController extends BaseController { 8 | 9 | @inject() 10 | ctx: Context; 11 | 12 | @plugin() 13 | jwt: any; 14 | 15 | @inject('UserService') 16 | user: IUserService; 17 | /** 18 | * 处理ocr数据转发 19 | */ 20 | @post('/') 21 | async index() { 22 | try { 23 | const state = this.ctx.state; 24 | console.log(state, 'state'); 25 | this.success(state.user.user); 26 | } catch (e) { 27 | this.fail('server error'); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/src/interface/service/IAccountService.ts: -------------------------------------------------------------------------------- 1 | import { IAccountInfo } from '../IAccountInfo'; 2 | import { ILoginResult } from '../ILoginResult'; 3 | 4 | export interface IAccountService { 5 | /** 6 | * User login 7 | * @param {IAccountInfo} accountInfo - account information 8 | * @returns {Promise} 9 | */ 10 | login(accountInfo: IAccountInfo): Promise; 11 | 12 | /** 13 | * Check user exists 14 | * @param {IAccountInfo} userInfo - user info 15 | * @returns {Promise} 16 | */ 17 | authUser(userInfo: IAccountInfo): Promise; 18 | 19 | /** 20 | * User register 21 | * @param {IAccountInfo} accountInfo - account information 22 | * @returns {Promise} 23 | */ 24 | register(accountInfo: IAccountInfo): Promise; 25 | } 26 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "inlineSourceMap": true, 8 | "module": "commonjs", 9 | "newLine": "lf", 10 | "noFallthroughCasesInSwitch": true, 11 | "noUnusedLocals": true, 12 | "outDir": "dist", 13 | "pretty": true, 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "strictPropertyInitialization": false, 17 | "stripInternal": true, 18 | "strictNullChecks": true, 19 | "suppressImplicitAnyIndexErrors": true, 20 | "target": "ES2018" 21 | }, 22 | "exclude": [ 23 | "app/public", 24 | "app/views", 25 | "dist", 26 | "node_modules*", 27 | "test", 28 | "**/*.d.ts", 29 | "**/*.spec.ts" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /server/src/app.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | import * as path from 'path'; 3 | import ElkTransport from './app/helper/logTransport'; 4 | 5 | export default (app: Application) => { 6 | app.beforeStart(async () => { 7 | }); 8 | app.use(async (ctx, next) => { 9 | try { 10 | await next(); 11 | } catch (err) { 12 | if (err.name === 'UnauthorizedError') { 13 | ctx.status = 401; 14 | ctx.body = { 15 | code: '999999', 16 | data: {}, 17 | message: 'invalid token...', 18 | }; 19 | ctx.logger.error('invalid token...', err); 20 | } 21 | } 22 | }); 23 | app.logger.set('remoteInfo', new ElkTransport({ 24 | level: 'INFO', 25 | file: path.join(app.baseDir, '../logs/ELKLog/info.log'), 26 | })); 27 | app.logger.set('remoteError', new ElkTransport({ 28 | level: 'ERROR', 29 | file: path.join(app.baseDir, '../logs/ELKLog/error.log'), 30 | })); 31 | }; 32 | -------------------------------------------------------------------------------- /server/src/app/extend/context.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | 3 | const LOG_COLLECTION = Symbol('Context#logCollection'); 4 | export default { 5 | context: this, 6 | /** 7 | * Get log 8 | * @returns {any} 9 | */ 10 | getLogs(this: Context) { 11 | const result: any = {}; 12 | for (const [ k, val ] of this.logCollection) { 13 | result[k] = val; 14 | } 15 | return result; 16 | }, 17 | /** 18 | * Collect log info 19 | * @param {string} key 20 | * @param {string} value 21 | */ 22 | setLogCollection(this: Context, key: string, value: string) { 23 | this.logCollection.set(key, value); 24 | }, 25 | get logCollection(this: Context) { 26 | if (!this.context[LOG_COLLECTION]) { 27 | this.context[LOG_COLLECTION] = new Map(); 28 | } 29 | return this.context[LOG_COLLECTION]; 30 | }, 31 | set logCollection(this: Context, value) { 32 | this.context[LOG_COLLECTION] = value; 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /client/src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios, {AxiosRequestConfig, Method} from 'axios'; 2 | import cookie from 'js-cookie'; 3 | import origin from '@/utils/origin'; 4 | 5 | const request = async ({method = 'post' as Method, url = '', body = {}, timeout = 8000}) => { 6 | if (!url) { 7 | return Promise.reject('Request url is null!'); 8 | } 9 | const token = cookie.get('token') || localStorage.getItem('token'); 10 | const headers = { 11 | Authorization: `Bearer ${token}`, 12 | }; 13 | console.log('url', origin.url); 14 | url = `${origin.url}/node${url}`; 15 | const option: AxiosRequestConfig = { 16 | url, 17 | method, 18 | timeout, 19 | data: body, 20 | withCredentials: true, 21 | headers, 22 | }; 23 | try { 24 | const result = await axios(option); 25 | if (result.data.code === '000000') { 26 | return result.data; 27 | } else { 28 | throw result.data; 29 | } 30 | } catch (e) { 31 | throw e; 32 | } 33 | }; 34 | export default request; 35 | -------------------------------------------------------------------------------- /client/src/components/msgList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{msg.message}} 5 | 6 | 7 | 8 | 9 | 18 | 40 | -------------------------------------------------------------------------------- /server/src/app/middleware/elkLogger.ts: -------------------------------------------------------------------------------- 1 | import { Context, EggAppConfig } from 'egg'; 2 | 3 | /** 4 | * elk 5 | * @description request intercept,log report,prod default open 6 | * @param {EggAppConfig["elkLogger"]} options config elkLogger 7 | * @returns {any} 8 | */ 9 | export default function elkLogger(options: EggAppConfig['elkLogger']): any { 10 | return async (ctx: Context, next: () => Promise) => { 11 | const { match, enable } = options; 12 | // match 13 | if (match(ctx) && enable) { 14 | ctx.setLogCollection('fetchStart', Date.now()); 15 | ctx.setLogCollection('url', ctx.url); 16 | ctx.setLogCollection('requestBody', ctx.request.body); 17 | ctx.setLogCollection('message', `${ctx.request.method} ${ctx.url} info`); 18 | ctx.setLogCollection('level', 'INFO'); 19 | await next(); 20 | ctx.setLogCollection('status', ctx.res.statusCode); 21 | ctx.setLogCollection('fetchEnd', Date.now()); 22 | ctx.logger.info(ctx.getLogs()); 23 | } 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Texas Poker Game 2 | ================ 3 | > This is an online Texas Hold'em game, base on TypeScript,Egg,Node.js,Vue 4 | ## Server 5 | > base on midway.js,TypeScript, socket.io, mysql. 6 | 7 | Detail: [server-readme](https://github.com/wzdwc/TexasPokerGame/tree/master/server) 8 | ## Client 9 | > base on vue-cli, TypeScript, socket.io. 10 | 11 | Detail: [client-readme](https://github.com/wzdwc/TexasPokerGame/tree/master/client) 12 | ## Project structure 13 | ``` 14 | ├─client 15 | ├─database 16 | │ └─poker.sql 17 | └─server 18 | ``` 19 | ## Demo 20 | See: [demo](http://www.jojgame.com) 21 |  22 | 23 |  24 | 25 |  26 | 27 |  28 | 29 |  30 | ## License 31 | The MIT License (MIT) 32 | -------------------------------------------------------------------------------- /server/src/service/player.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IPlayerDTO, 3 | IPlayerService, 4 | UpdatePlayerDTO, 5 | } from '../interface/IPlayer'; 6 | import { Context, inject, plugin, provide } from 'midway'; 7 | 8 | @provide('PlayerRecordService') 9 | export class PlayerService implements IPlayerService { 10 | 11 | @inject() 12 | ctx: Context; 13 | 14 | @plugin() 15 | mysql: any; 16 | 17 | async add(gameRecord: IPlayerDTO) { 18 | return await this.mysql.insert('player', { 19 | ...gameRecord, 20 | }); 21 | } 22 | 23 | async update(updatePlayer: UpdatePlayerDTO) { 24 | const row = { 25 | id: updatePlayer.playerId, 26 | counter: updatePlayer.counter, 27 | }; 28 | return await this.mysql.update('player', row); 29 | } 30 | 31 | async findByRoomNumber(roomNumber: number): Promise { 32 | const result = await this.mysql.select('player', { 33 | where: { roomNumber }, 34 | }); 35 | console.log(result); 36 | return result ? JSON.parse(JSON.stringify(result)) : []; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/src/service/user.ts: -------------------------------------------------------------------------------- 1 | import { Context, inject, provide, plugin } from 'midway'; 2 | import { IUser } from '../interface/IUser'; 3 | import { IUserService } from '../interface/service/IUserService'; 4 | import { IAccountInfo } from '../interface/IAccountInfo'; 5 | 6 | @provide('UserService') 7 | export class UserService implements IUserService { 8 | 9 | @inject() 10 | ctx: Context; 11 | 12 | @plugin() 13 | mysql: any; 14 | 15 | async findById(uid: string): Promise { 16 | const user = await this.mysql.get('user', { id: uid }); 17 | return user; 18 | } 19 | 20 | async findByAccount(account: string) { 21 | const user = await this.mysql.get('user', { account }); 22 | return user; 23 | } 24 | 25 | async addUser(accountInfo: IAccountInfo): Promise { 26 | const user = await this.mysql.insert('user', { 27 | account: accountInfo.userAccount, 28 | password: accountInfo.password, 29 | nickName: accountInfo.nickName, 30 | }); 31 | return { succeed: user.affectedRows === 1 }; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /server/test/utils/link.test.ts: -------------------------------------------------------------------------------- 1 | // import { Link } from '../../src/utils/Link'; 2 | // import { Player } from '../../src/app/core/Player'; 3 | // 4 | // describe('test/utils/link.test.ts', () => { 5 | // it('link', async () => { 6 | // const person1 = new Player({ 7 | // buyIn: 0, 8 | // counter: 1, position: 1, userId: '1', socketId: '', account: '', nickName: '' }); 9 | // const person2 = new Player({ 10 | // buyIn: 0, 11 | // counter: 2, position: 2, userId: '2', socketId: '', account: '', nickName: '' }); 12 | // const person3 = new Player({ 13 | // buyIn: 0, 14 | // counter: 2, position: 3, userId: '3', socketId: '', account: '', nickName: '' }); 15 | // const person4 = new Player({ 16 | // buyIn: 0, 17 | // counter: 2, position: 4, userId: '4', socketId: '', account: '', nickName: '' }); 18 | // // const person5 = new Player({ counter: 2, position: 5, userId: '5' }); 19 | // const link = new Link([ person1, person2, person3, person4 ], false); 20 | // console.log(link.removeNode(0)); 21 | // }); 22 | // 23 | // }); 24 | -------------------------------------------------------------------------------- /server/src/app/controller/room.ts: -------------------------------------------------------------------------------- 1 | import { Context, inject, controller, post, provide } from 'midway'; 2 | import BaseController from '../../lib/baseController'; 3 | import { IRoomService } from '../../interface/IRoom'; 4 | 5 | @provide() 6 | @controller('/node/game/room') 7 | export class RoomController extends BaseController { 8 | 9 | @inject() 10 | ctx: Context; 11 | 12 | @inject('RoomService') 13 | roomService: IRoomService; 14 | /** 15 | * 16 | */ 17 | @post('/') 18 | async index() { 19 | try { 20 | const { body } = this.getRequestBody(); 21 | const result = await this.roomService.add(body.isShort, body.smallBlind); 22 | this.success(result); 23 | } catch (e) { 24 | this.fail('create room error'); 25 | console.log(e); 26 | } 27 | } 28 | 29 | @post('/find') 30 | async find() { 31 | try { 32 | const { body } = this.getRequestBody(); 33 | const result = await this.roomService.findRoomNumber(body.roomNumber); 34 | this.success({ ...result }); 35 | } catch (e) { 36 | this.fail('invalid room'); 37 | console.log(e); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/src/interface/Ilog.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 系统日志属性 3 | */ 4 | export interface OSLogField { 5 | /** 6 | * 进程识别号 7 | */ 8 | pid: number; 9 | /** 10 | * node 版本号 11 | */ 12 | nodeVersion: string; 13 | /** 14 | * 启动时间 15 | */ 16 | launchTime: string; 17 | /** 18 | * 系统用户名 19 | */ 20 | osUser: string; 21 | /** 22 | * 系统用户id 23 | */ 24 | osUid: number; 25 | } 26 | 27 | /** 28 | * 业务日志属性 29 | */ 30 | export interface BusinessLogField { 31 | /** 32 | * 日志时间戳 33 | */ 34 | timestamp: string; 35 | /** 36 | * 请求时间戳 37 | */ 38 | requestTime: string; 39 | /** 40 | * 请求总数 41 | */ 42 | total: number; 43 | /** 44 | * 请求url 45 | */ 46 | url: string; 47 | /** 48 | * 请求状态码 49 | */ 50 | status: string; 51 | /** 52 | * 请求消耗时间 53 | */ 54 | fetchConsumeTime: number; 55 | /** 56 | * 请求描述 57 | */ 58 | message: string; 59 | /** 60 | * 日志级别 61 | */ 62 | level: string; 63 | /** 64 | * 请求错误栈 65 | */ 66 | stack: string; 67 | /** 68 | * 请求信息 69 | */ 70 | requestBody?: any; 71 | /** 72 | * 请求类型 73 | */ 74 | method: string; 75 | } 76 | -------------------------------------------------------------------------------- /server/src/lib/baseController.ts: -------------------------------------------------------------------------------- 1 | import { inject, Context } from 'midway'; 2 | import { IResult, ResultCode } from '../interface/IResult'; 3 | 4 | export default class BaseController { 5 | 6 | @inject() 7 | protected ctx: Context; 8 | 9 | /** 10 | * 获取请求内容 11 | * @returns {IRequestBody} 12 | */ 13 | public getRequestBody() { 14 | // let params: IRequestBody; 15 | // params = this.ctx.request.body.params && JSON.parse(this.ctx.request.body.params) || {}; 16 | // console.log(this.ctx.request.body, 'params'); 17 | console.log(this.ctx.request, 'request'); 18 | return this.ctx.request; 19 | } 20 | 21 | /** 22 | * 失败回调封装 23 | * @param {Object} data 24 | */ 25 | public success(data: any) { 26 | const result: IResult = { 27 | code: ResultCode.SUCCESS, 28 | data, 29 | message: 'successful', 30 | }; 31 | this.ctx.body = result; 32 | } 33 | /** 34 | * 错误回调封装 35 | * @param {string} message 36 | */ 37 | public fail(message: string) { 38 | const result: IResult = { 39 | code: ResultCode.FAIL, 40 | data: {}, 41 | message, 42 | }; 43 | this.ctx.body = result; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poke-game-front-ts", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "dev": "cross-env NODE_ENV=develop vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "@types/fastclick": "^1.0.29", 13 | "@types/js-cookie": "^2.2.6", 14 | "@types/socket.io-client": "^1.4.32", 15 | "axios": "^0.19.2", 16 | "core-js": "^3.6.4", 17 | "cross-env": "^7.0.2", 18 | "fastclick": "^1.0.6", 19 | "js-cookie": "^2.2.1", 20 | "socket.io-client": "^2.3.0", 21 | "vconsole": "^3.3.4", 22 | "vue": "^2.6.11", 23 | "vue-class-component": "^7.2.3", 24 | "vue-property-decorator": "^8.4.1", 25 | "vue-router": "^3.1.6", 26 | "vuex": "^3.1.3", 27 | "vux": "^2.9.4" 28 | }, 29 | "devDependencies": { 30 | "@vue/cli-plugin-babel": "^4.3.0", 31 | "@vue/cli-plugin-typescript": "^4.3.0", 32 | "@vue/cli-service": "^4.3.0", 33 | "less": "^3.0.4", 34 | "less-loader": "^5.0.0", 35 | "postcss-px-to-viewport": "^1.1.1", 36 | "typescript": "~3.8.3", 37 | "vue-template-compiler": "^2.6.11" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server/src/lib/baseService.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'egg'; 2 | import { IFetchOptions } from '../interface/IFetchOptions'; 3 | import { inject, config } from 'midway'; 4 | 5 | export default class BaseService { 6 | @inject() 7 | protected ctx: Context; 8 | 9 | @config('apiDomain') 10 | protected apiDomainConfig: any; 11 | 12 | /** 13 | * 处理请求 14 | * @param {IFetchOptions} option 15 | * @returns {Promise} 16 | */ 17 | public async fetch(option: IFetchOptions) { 18 | try { 19 | const data = { 20 | head: { ...option.head }, 21 | body: { ...option.body }, 22 | }; 23 | // 用户登录标识 24 | const headers = { 25 | Authorization: `Bearer ${option.ssjToken}`, 26 | }; 27 | const ajaxUrl = this.apiDomainConfig.loanDomain + option.url; 28 | // 发起服务请求 29 | const result = await this.ctx.curl(ajaxUrl, { 30 | data, 31 | type: option.type || 'POST', 32 | headers, 33 | dataType: 'json', 34 | }); 35 | // 接口请求失败日志上报 36 | if (result.status !== 200) { 37 | this.ctx.logger.error(this.ctx.getLogs()); 38 | } 39 | return result.data; 40 | } catch (e) { 41 | this.ctx.logger.error(this.ctx.getLogs()); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /client/src/service/index.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export default { 4 | register: ({ userAccount = '', password = '', nickName = '' }) => request({ 5 | url: '/user/register', 6 | body: { userAccount, password, nickName }, 7 | }), 8 | login: (userAccount: string, password: string ) => request({ 9 | url: '/user/login', 10 | body: { userAccount, password }, 11 | }), 12 | checkLogin: () => request({ 13 | url: '/user', 14 | body: {}, 15 | }), 16 | createRoom: (isShort: boolean, smallBlind: number, time: number) => request({ 17 | url: '/game/room', 18 | body: { isShort, smallBlind, time }, 19 | }), 20 | findRoom: (roomNumber: string) => request({ 21 | url: '/game/room/find', 22 | body: { roomNumber }, 23 | }), 24 | buyIn: (buyInSize: number) => request({ 25 | url: '/game/buyIn', 26 | body: { buyInSize }, 27 | }), 28 | commandRecordList: (roomNumber: string, gameId: number) => request({ 29 | url: '/game/record/find/commandRecord', 30 | body: { roomNumber, gameId }, 31 | }), 32 | gameRecordList: (roomNumber: string) => request({ 33 | url: '/game/record/find/gameRecord', 34 | body: { roomNumber }, 35 | }), 36 | selfPast7DayGame: (userID: number) => request({ 37 | url: '/game/record/find/selfPast7DayGame', 38 | body: { userID }, 39 | }), 40 | }; 41 | -------------------------------------------------------------------------------- /client/src/assets/gold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/src/service/game.ts: -------------------------------------------------------------------------------- 1 | import { Context, inject, plugin, provide } from 'midway'; 2 | import { IGame, IGameService } from '../interface/IGame'; 3 | 4 | @provide('GameService') 5 | export class GameService implements IGameService { 6 | 7 | @inject() 8 | ctx: Context; 9 | 10 | @plugin() 11 | mysql: any; 12 | 13 | async add(game: IGame) { 14 | console.log('this.mysql', this.mysql); 15 | const gameInfo = await this.mysql.insert('game', { 16 | ...game, 17 | }); 18 | console.log(gameInfo); 19 | return { succeed: gameInfo.affectedRows === 1, id: gameInfo.insertId }; 20 | } 21 | 22 | async update(game: IGame) { 23 | const gameInfo = await this.mysql.update('game', { 24 | ...game, 25 | }); 26 | console.log(gameInfo); 27 | return { succeed: gameInfo.affectedRows === 1 }; 28 | } 29 | 30 | async findByID(gid: number): Promise { 31 | return await this.mysql.get('game', { id: gid }); 32 | } 33 | 34 | async findByIDs(ids: number[]): Promise { 35 | return await this.mysql.select('game', { 36 | where: { id: ids }, 37 | }); 38 | } 39 | 40 | async findByRoomNumber(roomNumber: number): Promise { 41 | const result = await this.mysql.select('game', { 42 | where: { roomNumber }, 43 | }); 44 | console.log(result, 'game -======================'); 45 | return JSON.parse(JSON.stringify(result)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/src/components/Audio.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Your browser does not support the audio element. 6 | 7 | 8 | 9 | Your browser does not support the audio element. 10 | 11 | 12 | 13 | Your browser does not support the audio element. 14 | 15 | 16 | 17 | Your browser does not support the audio element. 18 | 19 | 20 | 21 | 22 | 37 | 38 | 39 | 47 | -------------------------------------------------------------------------------- /server/src/app/io/controller/nsp.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { Controller } from 'egg'; 4 | 5 | class NspController extends Controller { 6 | async exchange() { 7 | const { ctx } = this; 8 | const socket = ctx.socket as any; 9 | const app = ctx.app as any; 10 | const nsp = app.io.of('/socket'); 11 | const message = ctx.args[0] || {}; 12 | const client = socket.id; 13 | try { 14 | const { target, payload } = message; 15 | if (!target) return; 16 | const msg = ctx.helper.parseMsg('exchange', payload, { client, target }); 17 | nsp.emit(target, msg); 18 | } catch (error) { 19 | app.logger.error(error); 20 | } 21 | } 22 | 23 | async broadcast() { 24 | const { ctx } = this; 25 | const socket = ctx.socket as any; 26 | const app = ctx.app as any; 27 | const nsp = app.io.of('/socket'); 28 | const message = ctx.args[0] || {}; 29 | const { room } = socket.handshake.query; 30 | const rooms = [ room ]; 31 | try { 32 | const { payload } = message; 33 | nsp.adapter.clients(rooms, (err: any, clients: any) => { 34 | // 广播信息 35 | nsp.to(room).emit('online', { 36 | clients, 37 | action: 'broadcast', 38 | target: 'participator', 39 | message: payload, 40 | }); 41 | }); 42 | } catch (error) { 43 | app.logger.error(error); 44 | } 45 | } 46 | } 47 | 48 | module.exports = NspController; 49 | -------------------------------------------------------------------------------- /server/src/app/controller/account.ts: -------------------------------------------------------------------------------- 1 | import BaseController from '../../lib/baseController'; 2 | import { controller, inject, post, provide } from 'midway'; 3 | import { IAccountService } from '../../interface/service/IAccountService'; 4 | import { IAccountInfo } from '../../interface/IAccountInfo'; 5 | 6 | @provide() 7 | @controller('/node/user/') 8 | export class Account extends BaseController { 9 | 10 | @inject('AccountService') 11 | service: IAccountService; 12 | 13 | @post('/login') 14 | async login() { 15 | try { 16 | const { body } = this.getRequestBody(); 17 | console.log(body, 'body'); 18 | const { userAccount, password } = body; 19 | console.log(userAccount, 'userAccount'); 20 | const accountInfo: IAccountInfo = { userAccount, password }; 21 | const result = await this.service.login(accountInfo); 22 | this.success(result); 23 | } catch (e) { 24 | this.ctx.logger.error('login-----:', e); 25 | this.fail(e); 26 | } 27 | } 28 | 29 | @post('/register') 30 | async register() { 31 | try { 32 | const { body } = this.getRequestBody(); 33 | console.log(body); 34 | const { userAccount, password, nickName } = body; 35 | const accountInfo: IAccountInfo = { userAccount, password, nickName }; 36 | const result = await this.service.register(accountInfo); 37 | this.success(result); 38 | } catch (e) { 39 | this.ctx.logger.error('login-----:', e); 40 | this.fail(e); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /server/src/service/room.ts: -------------------------------------------------------------------------------- 1 | import { Context, inject, provide, plugin } from 'midway'; 2 | import { IRoom, IRoomService } from '../interface/IRoom'; 3 | 4 | @provide('RoomService') 5 | export default class RoomService implements IRoomService { 6 | 7 | @inject() 8 | ctx: Context; 9 | 10 | @plugin() 11 | mysql: any; 12 | 13 | @plugin() 14 | redis: any; 15 | 16 | async findById(uid: string): Promise { 17 | return await this.mysql.get('room', { id: uid }); 18 | } 19 | 20 | async findRoomNumber(roomNumber: string): Promise { 21 | const result = await this.mysql.get('room', { roomNumber }); 22 | return { 23 | isShort: !!result.isShort, 24 | smallBlind: result.smallBlind, 25 | time: result.time, 26 | }; 27 | } 28 | 29 | async findByRoomNumber(number: string): Promise { 30 | const roomNumber = await this.redis.get(`room:${number}`); 31 | return !!roomNumber; 32 | } 33 | 34 | async add(isShort: boolean, smallBlind: number, expires: number = 360000) { 35 | const number = Math.floor(Math.random() * (1000000 - 100000)) + 100000; 36 | const result = await this.mysql.insert('room', { 37 | roomNumber: number, 38 | time: expires, 39 | isShort, 40 | smallBlind, 41 | }); 42 | const roomRedis = await this.redis.set(`room:${number}`, `${number}`, 'ex', expires); 43 | if (result.affectedRows === 1 && roomRedis === 'OK') { 44 | return { roomNumber: number }; 45 | } else { 46 | throw 'room add error'; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /server/src/app/core/Poker.ts: -------------------------------------------------------------------------------- 1 | export interface IPoker { 2 | init(): void; 3 | 4 | getCard(): string; 5 | 6 | getRandom(number: number): number; 7 | } 8 | 9 | /** 10 | * Created by jorky on 2020/2/23. 11 | */ 12 | export class Poker implements IPoker { 13 | private pokers: string [] = []; 14 | private readonly isShort: boolean; 15 | 16 | constructor(isShort = false) { 17 | this.isShort = isShort; 18 | this.init(); 19 | } 20 | 21 | init(): void { 22 | let size = [ 23 | 'a', 24 | 'b', 25 | 'c', 26 | 'd', 27 | 'e', 28 | 'f', 29 | 'g', 30 | 'h', 31 | 'i', 32 | 'j', 33 | 'k', 34 | 'l', 35 | 'm' ]; 36 | if (this.isShort) { 37 | size = [ 38 | 'e', 39 | 'f', 40 | 'g', 41 | 'h', 42 | 'i', 43 | 'j', 44 | 'k', 45 | 'l', 46 | 'm' ]; 47 | } 48 | const color = [ 1, 2, 3, 4 ]; 49 | for (const i of size) { 50 | for (const j of color) { 51 | this.pokers.push(`${i}${j}`); 52 | } 53 | } 54 | } 55 | 56 | getCard(): string { 57 | if (this.pokers.length === 0) return 'done'; 58 | const currCardIndex = this.getRandom(this.pokers.length); 59 | const currCard = this.pokers[currCardIndex]; 60 | this.pokers.splice(currCardIndex, 1); 61 | return currCard; 62 | } 63 | 64 | getRandom(number: number): number { 65 | const maxNumber = Math.ceil(number); 66 | return Math.floor(Math.random() * maxNumber); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /client/src/assets/less/base.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'iconfont'; /* project id 1801313 */ 3 | src: url('//at.alicdn.com/t/font_1801313_jpwi7jzmcwr.eot'); 4 | src: url('//at.alicdn.com/t/font_1801313_jpwi7jzmcwr.eot?#iefix') format('embedded-opentype'), 5 | url('//at.alicdn.com/t/font_1801313_jpwi7jzmcwr.woff2') format('woff2'), 6 | url('//at.alicdn.com/t/font_1801313_jpwi7jzmcwr.woff') format('woff'), 7 | url('//at.alicdn.com/t/font_1801313_jpwi7jzmcwr.ttf') format('truetype'), 8 | url('//at.alicdn.com/t/font_1801313_jpwi7jzmcwr.svg#iconfont') format('svg'); 9 | } 10 | 11 | .iconfont { 12 | font-family: "iconfont" !important; 13 | font-size: 16px; 14 | font-style: normal; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | .icon-account:before { 20 | content: "\e60a"; 21 | } 22 | 23 | .icon-user-avatar:before { 24 | content: "\e636"; 25 | } 26 | 27 | .icon-pot:before { 28 | content: "\e60c"; 29 | } 30 | 31 | .icon-gold:before { 32 | content: "\e609"; 33 | } 34 | .icon-setting:before { 35 | content: "\e611"; 36 | } 37 | .icon-password:before { 38 | content: "\e603"; 39 | } 40 | 41 | .icon-close:before { 42 | content: "\e615"; 43 | } 44 | 45 | .icon-msg:before{ 46 | content: "\e781"; 47 | } 48 | 49 | .icon-record:before{ 50 | content: "\e657"; 51 | } 52 | 53 | .icon-arrow:before{ 54 | content: "\e623"; 55 | } 56 | 57 | .icon-clock:before{ 58 | content: "\e608"; 59 | } 60 | 61 | 62 | body, p,h1,h2,ul,li,input{ 63 | padding: 0; 64 | margin: 0; 65 | } 66 | input{ 67 | border: 0; 68 | outline: none; 69 | } 70 | 71 | -------------------------------------------------------------------------------- /client/src/components/Notice.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{message.message}} 8 | 9 | 10 | 11 | 12 | 20 | 21 | 22 | 60 | -------------------------------------------------------------------------------- /client/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter, { RouteConfig } from 'vue-router'; 3 | import Home from '../views/home.vue'; 4 | import Login from '../views/login.vue'; 5 | import Register from '../views/register.vue'; 6 | import Game from '../views/game.vue'; 7 | import service from '../service'; 8 | import cookie from 'js-cookie'; 9 | 10 | Vue.use(VueRouter); 11 | 12 | const routes: RouteConfig[] = [ 13 | { 14 | path: '/', 15 | name: 'home', 16 | component: Home, 17 | meta: { 18 | title: 'home', 19 | needLogin: true, 20 | }, 21 | }, 22 | { 23 | path: '/login', 24 | name: 'login', 25 | component: Login, 26 | meta: { 27 | title: 'login', 28 | }, 29 | }, 30 | { 31 | path: '/register', 32 | name: 'register', 33 | component: Register, 34 | meta: { 35 | title: 'create account', 36 | }, 37 | }, 38 | { 39 | path: '/game/:roomNumber/:isOwner?', 40 | name: 'game', 41 | component: Game, 42 | meta: { 43 | title: 'game', 44 | needLogin: true, 45 | }, 46 | }, 47 | ]; 48 | 49 | const router = new VueRouter({ 50 | routes, 51 | }); 52 | 53 | router.beforeEach(async (to, from, next) => { 54 | if (to.meta.title) { 55 | document.title = to.meta.title; 56 | } 57 | if (to.meta.needLogin) { 58 | try { 59 | const result = await service.checkLogin(); 60 | console.log(result); 61 | cookie.set('user_id', result.data.userId); 62 | next(); 63 | } catch (e) { 64 | await router.replace({ name: 'login' }); 65 | } 66 | } else { 67 | next(); 68 | } 69 | }); 70 | 71 | export default router; 72 | -------------------------------------------------------------------------------- /client/src/components/CardStyle.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 40 | 41 | 42 | 50 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 75 | -------------------------------------------------------------------------------- /client/src/components/Toast.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | {{text}} 6 | 7 | 8 | 9 | 10 | 43 | 44 | 45 | 63 | -------------------------------------------------------------------------------- /server/src/app/io/middleware/auth.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'midway'; 2 | import { ITickMsg } from '../../../interface/ITickMsg'; 3 | 4 | export default function auth(): any { 5 | return async (ctx: Context, next: () => Promise) => { 6 | const socket = ctx.socket as any; 7 | const id = socket.id; 8 | const app = ctx.app as any; 9 | const nsp = app.io.of('/socket'); 10 | const query = socket.handshake.query; 11 | // 用户信息 12 | const { room, token } = query; 13 | function tick(id: number, msg: ITickMsg, nsp: any, socket: any) { 14 | // 踢出用户前发送消息 15 | socket.emit(id, ctx.helper.parseMsg('deny', msg)); 16 | // 调用 adapter 方法踢出用户,客户端触发 disconnect 事件 17 | nsp.adapter.remoteDisconnect(id, true, (err: any) => { 18 | ctx.logger.error('room service tick', err); 19 | }); 20 | } 21 | 22 | function leave() { 23 | } 24 | try { 25 | await app.jwt.verify(token); 26 | // const { nick_name: userName } = userInfo.user; 27 | 28 | // 检查房间是否存在,不存在则踢出用户 29 | const roomService = await app.applicationContext.getAsync('RoomService'); 30 | const hasRoom = await roomService.findByRoomNumber(room); 31 | if (!hasRoom) { 32 | tick(id, { 33 | type: 'deleted', 34 | message: 'deleted, room has been deleted.', 35 | }, nsp, socket); 36 | return; 37 | } 38 | console.log('play------------', room); 39 | await next(); 40 | leave(); 41 | } catch (e) { 42 | console.log(e); 43 | tick(id, { 44 | type: 'deleted', 45 | message: 'deleted, room has been deleted.', 46 | }, nsp, socket); 47 | } 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /client/src/plugins/toast.ts: -------------------------------------------------------------------------------- 1 | import Vue, { PluginObject } from 'vue'; 2 | import Toast from '../components/Toast.vue'; 3 | 4 | export interface IOptions { 5 | text?: string; 6 | timeOut?: number; 7 | } 8 | 9 | const ToastConstructor = Vue.extend(Toast); 10 | 11 | export class ToastExtendConstructor extends ToastConstructor { 12 | public close() { 13 | this.$props.show = false; 14 | this.$off('update:show'); 15 | } 16 | } 17 | 18 | let instance: ToastExtendConstructor; 19 | let defaultOptions: IOptions; 20 | 21 | const getInstance = () => { 22 | if (instance) { return instance; } 23 | 24 | instance = new ToastExtendConstructor({ 25 | el: document.createElement('div'), 26 | }); 27 | 28 | return instance; 29 | }; 30 | 31 | const toast = (options: string | IOptions) => { 32 | const vm = getInstance(); 33 | 34 | if (!defaultOptions) { 35 | defaultOptions = { ...vm.$props }; 36 | } 37 | 38 | let opts: IOptions; 39 | if (typeof options === 'string') { 40 | opts = { ...defaultOptions, text: options }; 41 | } else { 42 | opts = { ...defaultOptions, ...options }; 43 | } 44 | 45 | Object.keys(opts).forEach((key) => { 46 | vm.$props[key] = opts[key as keyof IOptions]; 47 | }); 48 | console.log(vm.$props); 49 | vm.$props.show = true; 50 | vm.$off('update:show'); 51 | vm.$on('update:show', (val: boolean) => { 52 | vm.$props.show = val; 53 | }); 54 | document.body.appendChild(vm.$el); 55 | 56 | return vm; 57 | }; 58 | 59 | const plugin: PluginObject = { 60 | // tslint:disable-next-line:no-shadowed-variable 61 | install(Vue) { 62 | if (!Vue.prototype.$plugin) { 63 | Vue.prototype.$plugin = {}; 64 | } 65 | Vue.prototype.$plugin.toast = toast; 66 | }, 67 | }; 68 | 69 | export default plugin; 70 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poke-game-server", 3 | "version": "1.0.0", 4 | "description": "node服务中心", 5 | "private": true, 6 | "dependencies": { 7 | "@types/socket.io": "^2.1.4", 8 | "chai": "^4.2.0", 9 | "egg-cors": "^2.2.3", 10 | "egg-jwt": "^3.1.7", 11 | "egg-mysql": "^3.0.0", 12 | "egg-redis": "^2.4.0", 13 | "egg-scripts": "^2.10.0", 14 | "egg-socket.io": "^4.1.6", 15 | "midway": "^1.0.0" 16 | }, 17 | "devDependencies": { 18 | "@types/mocha": "^5.2.7", 19 | "@types/node": "^10.5.5", 20 | "cross-env": "^6.0.0", 21 | "egg-ci": "^1.8.0", 22 | "midway-bin": "1", 23 | "midway-mock": "1", 24 | "ts-node": "^8.3.0", 25 | "tslib": "^1.8.1", 26 | "tslint": "^5.11.0", 27 | "tslint-config-egg": "^1.0.0", 28 | "typescript": "^3.5.0" 29 | }, 30 | "engines": { 31 | "node": ">=10.16.0" 32 | }, 33 | "scripts": { 34 | "start": "egg-scripts start --daemon --title=midway-server-node-loan-center --framework=midway --ts", 35 | "stop": "egg-scripts stop --title=midway-server-node-loan-center", 36 | "start_build": "npm run build && cross-env NODE_ENV=production midway-bin dev", 37 | "clean": "midway-bin clean", 38 | "dev": "cross-env NODE_ENV=local midway-bin dev --ts", 39 | "debug": "cross-env NODE_ENV=local midway-bin debug --ts", 40 | "test": "npm run lint && midway-bin test --ts", 41 | "cov": "midway-bin cov --ts", 42 | "lint": "tslint --fix -p tsconfig.json -t stylish", 43 | "ci": "npm run cov", 44 | "build": "midway-bin build -c" 45 | }, 46 | "ci": { 47 | "version": "10" 48 | }, 49 | "midway-bin-clean": [ 50 | ".vscode/.tsbuildinfo", 51 | "dist" 52 | ], 53 | "repository": { 54 | "type": "git", 55 | "url": "" 56 | }, 57 | "author": "Cai", 58 | "license": "MIT" 59 | } 60 | -------------------------------------------------------------------------------- /client/src/utils/Link.ts: -------------------------------------------------------------------------------- 1 | export interface ILinkNode { 2 | node: T; 3 | next: ILinkNode | null; 4 | } 5 | 6 | // interface ILink { 7 | // getNode(position: number): T; 8 | // setNode(position: number): void; 9 | // removeNode(position: number): void; 10 | 11 | export class Link { 12 | public link: ILinkNode = { 13 | node: {} as T, 14 | next: null, 15 | }; 16 | 17 | constructor(nodes: T[], isCircular: boolean = true) { 18 | let prevNode: ILinkNode = { 19 | node: {} as T, 20 | next: null, 21 | }; 22 | nodes.forEach((node, key) => { 23 | const currNode: ILinkNode = { 24 | node, 25 | next: null, 26 | }; 27 | // head 28 | if (key === 0) { 29 | this.link = currNode; 30 | } else { 31 | // circular, last node next is first 32 | if (key === nodes.length - 1 && isCircular) { 33 | currNode.next = this.link; 34 | } 35 | prevNode.next = currNode; 36 | } 37 | prevNode = currNode; 38 | }); 39 | } 40 | 41 | public getNode(position: number) { 42 | let linkNode = this.link; 43 | let i = 0; 44 | while (linkNode.next) { 45 | if (i === position) { 46 | return linkNode; 47 | } 48 | linkNode = linkNode.next; 49 | i++; 50 | } 51 | return linkNode; 52 | } 53 | 54 | public setNode(node: T, position: number) { 55 | let linkNode = this.link; 56 | let i = 0; 57 | const currNode: ILinkNode = { 58 | node, 59 | next: null, 60 | }; 61 | while (linkNode.next) { 62 | if (i === position) { 63 | currNode.next = linkNode.next; 64 | linkNode.next = currNode; 65 | return; 66 | } 67 | linkNode = linkNode.next; 68 | i++; 69 | } 70 | currNode.next = linkNode.next; 71 | linkNode.next = currNode; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /server/src/utils/Link.ts: -------------------------------------------------------------------------------- 1 | export interface ILinkNode { 2 | node: T; 3 | next: ILinkNode | null; 4 | } 5 | 6 | // interface ILink { 7 | // getNode(position: number): T; 8 | // setNode(position: number): void; 9 | // removeNode(position: number): void; 10 | 11 | export class Link { 12 | link: ILinkNode = { 13 | node: {} as T, 14 | next: null, 15 | }; 16 | 17 | constructor(nodes: T[], isCircular: boolean = true) { 18 | let prevNode: ILinkNode = { 19 | node: {} as T, 20 | next: null, 21 | }; 22 | nodes.forEach((node, key) => { 23 | const currNode: ILinkNode = { 24 | node, 25 | next: null, 26 | }; 27 | // head 28 | if (key === 0) { 29 | this.link = currNode; 30 | } else { 31 | // circular, last node next is first 32 | if (key === nodes.length - 1 && isCircular) { 33 | currNode.next = this.link; 34 | } 35 | prevNode.next = currNode; 36 | } 37 | prevNode = currNode; 38 | }); 39 | } 40 | 41 | getNode(position: number) { 42 | let linkNode = this.link; 43 | let i = 0; 44 | while (linkNode.next) { 45 | if (i === position) { 46 | return linkNode; 47 | } 48 | linkNode = linkNode.next; 49 | i++; 50 | } 51 | return linkNode; 52 | } 53 | 54 | setNode(node: T, position: number) { 55 | let linkNode = this.link; 56 | let i = 0; 57 | const currNode: ILinkNode = { 58 | node, 59 | next: null, 60 | }; 61 | while (linkNode.next) { 62 | if (i === position) { 63 | currNode.next = linkNode.next; 64 | linkNode.next = currNode; 65 | return; 66 | } 67 | linkNode = linkNode.next; 68 | i++; 69 | } 70 | currNode.next = linkNode.next; 71 | linkNode.next = currNode; 72 | } 73 | 74 | removeNode(position: number) { 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /server/src/service/commandRecord.ts: -------------------------------------------------------------------------------- 1 | import { Context, inject, plugin, provide } from 'midway'; 2 | import { 3 | ICommandRecord, 4 | ICommandRecordService, 5 | } from '../interface/ICommandRecord'; 6 | 7 | @provide('CommandRecordService') 8 | export class CommandRecord implements ICommandRecordService { 9 | 10 | @inject() 11 | ctx: Context; 12 | 13 | @plugin() 14 | mysql: any; 15 | 16 | async add(commandRecord: ICommandRecord) { 17 | const result = await this.mysql.insert('command_record', { 18 | ...commandRecord, 19 | }); 20 | return { succeed: result.affectedRows === 1 }; 21 | } 22 | 23 | async findPast7DayGameIDsByUserID(userID: number): Promise { 24 | const result = await this.mysql.query('SELECT\n' + 25 | 'DISTINCT gameId\n' + 26 | 'FROM command_record\n' + 27 | 'WHERE userId = ?\n' + 28 | 'AND create_time >= DATE_SUB(now(),interval 7 DAY)', [ userID ]); 29 | const recordList = JSON.parse(JSON.stringify(result)); 30 | if (recordList) { 31 | return recordList.map((item: ICommandRecord) => { 32 | return item.gameId; 33 | }); 34 | } 35 | return []; 36 | } 37 | 38 | async findByGameIDs(gameIDs: number[]): Promise { 39 | const result = await this.mysql.select('command_record', { 40 | where: { 41 | gameId: gameIDs, 42 | }, 43 | }); 44 | return JSON.parse(JSON.stringify(result)); 45 | } 46 | 47 | async findByGameID(gameID: number): Promise { 48 | const result = await this.mysql.query('SELECT\n' + 49 | '\tcommand_record.counter,\n' + 50 | '\tcommand_record.gameStatus,\n' + 51 | '\tcommand,\n' + 52 | '\thandCard,\n' + 53 | '\ttype,\n' + 54 | '\tcommonCard,\n' + 55 | '\tpot,\n' + 56 | '\tcommand_record.userId,\n' + 57 | '\t`user`.nickName\n' + 58 | 'FROM\n' + 59 | '\tcommand_record\n' + 60 | 'INNER JOIN `user` ON `user`.id = command_record.userId\n' + 61 | 'INNER JOIN player ON player.userId = command_record.userId\n' + 62 | '\twhere command_record.gameId = ? and player.gameId = ?', [ gameID, gameID ]); 63 | console.log(result, '=============command'); 64 | return JSON.parse(JSON.stringify(result)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /client/src/components/SendMsg.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | send 13 | 14 | 15 | 16 | 17 | 40 | 41 | 42 | 91 | -------------------------------------------------------------------------------- /client/src/components/Record.vue: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | record 8 | 9 | 10 | nickName 11 | buy in 12 | counter 13 | income 14 | 15 | 16 | {{player.nickName}} 17 | {{player.buyIn}} 18 | {{player.counter}} 19 | {{player.counter - player.buyIn}} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 47 | 48 | 49 | 102 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | poke-game-center 2 | ================ 3 | > pokeGame server,base on TypeScript,midway,node,mysql,redis 4 | 5 | ### Setup 6 | See: [egg document][eggjs], [midway document][midway]。 7 | 8 | - Redis for game room 9 | ``` 10 | // config.default 11 | config.redis = { 12 | client: { 13 | port: 6379, 14 | host: '127.0.0.1', 15 | password: '123456', 16 | db: 0, 17 | }, 18 | }; 19 | 20 | ``` 21 | - DataBase 22 | > Mysql 23 | ``` 24 | // config.default 25 | config.mysql = { 26 | client: { 27 | // mysql host 28 | host: '', 29 | // pot 30 | port: '3306', 31 | // userName 32 | user: 'root', 33 | // password 34 | password: '', 35 | // database name 36 | database: 'poker', 37 | }, 38 | app: true, 39 | agent: false, 40 | }; 41 | 42 | 43 | ``` 44 | - Install 45 | ```bash 46 | $ yarn 47 | $ yarn dev 48 | $ open http://localhost:7001/ 49 | ``` 50 | 51 | ### Deploy 52 | 53 | ```bash 54 | $ npm start 55 | $ npm stop 56 | ``` 57 | 58 | ### Test 59 | ``` 60 | yarn test 61 | 62 | ``` 63 | - See [midway document - test](https://eggjs.org/zh-cn/core/unittest)。 64 | 65 | ### Project structure 66 | ``` 67 | ├─dist 68 | ├─logs 69 | │ ├─ELKLog // report log 70 | | | ├─info.log 71 | | | └─error.log 72 | │ └─node-loan-center // system log 73 | ├─node_modules 74 | ├─src 75 | │ ├─app 76 | │ │ ├─controller // http controller 77 | │ │ ├─core // poker core code 78 | | | | ├─Player.ts // game player class 79 | | | | ├─Poker.ts // poker class, get random poker cards 80 | | | | ├─PokerGame.ts // poker game Class 81 | │ │ │ └─PokerStyle.ts // Contrast poker style and all TexasPoker style 82 | │ │ ├─extend 83 | │ │ ├─helper 84 | │ │ ├─io 85 | │ │ │ ├─controller // socket.io controller 86 | │ │ │ └─middleware //do auth, join, leave middleware 87 | │ │ ├─middleware // http middleware 88 | │ │ └─public // client 89 | │ ├─config // system base config 90 | │ ├─interface 91 | │ │ └─service 92 | │ ├─lib 93 | │ ├─service // http service 94 | │ └─utils // some tools 95 | └─test // test case 96 | └─app 97 | └─controller 98 | 99 | ``` 100 | 101 | [midway]: https://midwayjs.org 102 | [git-rules]: https://confluence.sui.work/pages/viewpage.action?pageId=51120607 103 | [eggjs]: https://eggjs.org/zh-cn/ 104 | 105 | ## License 106 | The MIT License (MIT) 107 | -------------------------------------------------------------------------------- /client/src/views/login.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | J-POKER 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | sign in 21 | sign up 22 | 23 | 24 | 25 | 26 | 55 | 90 | -------------------------------------------------------------------------------- /client/src/components/BuyIn.vue: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | buy in: 9 | 13 | 14 | buy in 15 | 16 | 17 | 18 | 19 | 56 | 57 | 58 | 107 | -------------------------------------------------------------------------------- /server/src/config/config.default.ts: -------------------------------------------------------------------------------- 1 | import { EggAppConfig, EggAppInfo, PowerPartial, Context } from 'midway'; 2 | 3 | export type DefaultConfig = PowerPartial; 4 | 5 | export default (appInfo: EggAppInfo) => { 6 | const config = {} as DefaultConfig; 7 | 8 | // use for cookie sign key, should change to your own and keep security 9 | config.keys = appInfo.name + '_{{keys}}'; 10 | 11 | // elk log,404 12 | config.middleware = [ 'elkLogger', 'notFound' ]; 13 | 14 | const bizConfig = { 15 | sourceUrl: '', 16 | elkLogger: { 17 | // request url match 18 | match(ctx: Context) { 19 | const reg = /.*/; 20 | return reg.test(ctx.url); 21 | }, 22 | enable: true, 23 | }, 24 | }; 25 | // security 26 | config.security = { 27 | csrf: { 28 | enable: false, 29 | }, 30 | methodnoallow: { 31 | enable: false, 32 | }, 33 | }; 34 | // CORS 35 | config.cors = { 36 | allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS', 37 | credentials: true, 38 | origin(ctx: Context) { 39 | const origin: string = ctx.get('origin'); 40 | // console.log(origin, 'orgin'); 41 | // access origin 42 | if (origin.indexOf('') > -1) { 43 | // console.log('come in'); 44 | return origin; 45 | } else { 46 | return '*'; 47 | } 48 | }, 49 | }; 50 | 51 | // logger 52 | config.logger = { 53 | outputJSON: false, 54 | appLogName: 'app.log', 55 | coreLogName: 'core.log', 56 | agentLogName: 'agent.log', 57 | errorLogName: 'error.log', 58 | }; 59 | 60 | // business domain 61 | config.apiDomain = {}; 62 | 63 | // jsonwebtoken 64 | config.jwt = { 65 | secret: '123456', 66 | enable: true, 67 | match(ctx: Context) { 68 | const reg = /login|register/; 69 | return !reg.test(ctx.originalUrl); 70 | }, 71 | }; 72 | 73 | // socket io setting 74 | config.io = { 75 | namespace: { 76 | '/socket': { 77 | connectionMiddleware: [ 'auth', 'join', 'leave' ], 78 | packetMiddleware: [], 79 | }, 80 | }, 81 | redis: { 82 | host: '127.0.0.1', 83 | port: 6379, 84 | password: '123456', 85 | }, 86 | }; 87 | 88 | config.redis = { 89 | client: { 90 | port: 6379, 91 | host: '127.0.0.1', 92 | password: '123456', 93 | db: 0, 94 | }, 95 | }; 96 | config.mysql = { 97 | client: { 98 | // host 99 | host: '127.0.0.1', 100 | // pot 101 | port: '3306', 102 | // userName 103 | user: 'root', 104 | // password 105 | password: '', 106 | // database name 107 | database: 'poker', 108 | }, 109 | app: true, 110 | agent: false, 111 | }; 112 | 113 | return { 114 | ...bizConfig, 115 | ...config, 116 | }; 117 | }; 118 | -------------------------------------------------------------------------------- /server/src/service/account.ts: -------------------------------------------------------------------------------- 1 | import BaseService from '../lib/baseService'; 2 | import { Context, inject, provide, plugin, config } from 'midway'; 3 | import { IAccountInfo } from '../interface/IAccountInfo'; 4 | import { IAccountService } from '../interface/service/IAccountService'; 5 | import { ILoginResult } from '../interface/ILoginResult'; 6 | import { IUserService } from '../interface/service/IUserService'; 7 | import { IUser } from '../interface/IUser'; 8 | 9 | @provide('AccountService') 10 | export class AccountService extends BaseService implements IAccountService { 11 | 12 | @inject() 13 | ctx: Context; 14 | 15 | @plugin() 16 | jwt: any; 17 | 18 | @inject('UserService') 19 | user: IUserService; 20 | 21 | @config('jwt') 22 | protected jwtConfig: any; 23 | 24 | public login(accountInfo: IAccountInfo): Promise { 25 | return new Promise(async (resolve, reject) => { 26 | try { 27 | let token = ''; 28 | // 校验用户信息 29 | const isAuth = await this.authUser(accountInfo); 30 | if (isAuth) { 31 | token = await this.getToken(accountInfo.userAccount); 32 | } 33 | const result: ILoginResult = { token }; 34 | resolve(result); 35 | } catch (e) { 36 | this.ctx.logger.error('login service error:', e); 37 | reject(e); 38 | } 39 | }); 40 | } 41 | 42 | public async register(accountInfo: IAccountInfo): Promise { 43 | return new Promise(async (resolve, reject) => { 44 | try { 45 | const hasUser = await this.checkHasUser(accountInfo.userAccount); 46 | console.log('accountInfo', hasUser, accountInfo); 47 | if (!hasUser) { 48 | const result = await this.user.addUser(accountInfo); 49 | if (result.succeed) { 50 | resolve('user create successful'); 51 | } 52 | } else { 53 | reject('User already exists'); 54 | } 55 | } catch (e) { 56 | this.ctx.logger.error('register service error:', e); 57 | reject(e); 58 | } 59 | }); 60 | } 61 | 62 | public async authUser(accountInfo: IAccountInfo) { 63 | const user: IUser = await this.checkHasUser(accountInfo.userAccount); 64 | const valid = user.password === accountInfo.password; 65 | if (!valid) { 66 | throw 'incorrect user account or password.'; 67 | } 68 | return valid; 69 | } 70 | 71 | private async checkHasUser(userAccount: string): Promise { 72 | return await this.user.findByAccount(userAccount); 73 | } 74 | 75 | private async getToken(userAccount: string) { 76 | const { nickName, account, id } = await this.user.findByAccount(userAccount); 77 | const token = this.jwt.sign({ 78 | user: { 79 | nickName, 80 | account, 81 | userId: id, 82 | }, 83 | }, 84 | this.jwtConfig.secret, { expiresIn: 60 * 60 * 24 * 360 }); 85 | this.ctx.logger.info(`AccountService getToken token--${token}`); 86 | return token; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /client/src/components/Range.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 49 | 50 | 51 | 98 | -------------------------------------------------------------------------------- /server/src/app/helper/logTransport.ts: -------------------------------------------------------------------------------- 1 | import { LoggerLevel, Context } from 'egg'; 2 | import { FileBufferTransport } from 'egg-logger'; 3 | import * as iconv from 'iconv-lite'; 4 | import * as moment from 'moment'; 5 | import * as os from 'os'; 6 | import { OSLogField, BusinessLogField } from '../../interface/Ilog'; 7 | 8 | const { uid, username } = os.userInfo(); 9 | 10 | /** 11 | * 日志格式化 12 | */ 13 | class LogFormat extends Map { 14 | logField: BusinessLogField; 15 | osField: OSLogField; 16 | constructor() { 17 | super(); 18 | this.osField = { 19 | pid: process.pid, 20 | nodeVersion: process.version, 21 | launchTime: moment().format('YYYY-MM-DD HH:mm:ss'), 22 | osUser: username, 23 | osUid: uid, 24 | }; 25 | } 26 | 27 | /** 28 | * Log日志进行utf-8编码 29 | */ 30 | protected toBuffer() { 31 | const fields = { ...this.osField, ...this.logField }; 32 | const str = JSON.stringify(fields) + '\n'; 33 | return iconv.encode(str, 'utf8'); 34 | } 35 | 36 | /** 37 | * 格式化Log上下文 38 | * @param logInfo 39 | * @param level 日志级别 40 | */ 41 | public formatFetchInfoMsg(logInfo: any[], level: LoggerLevel) { 42 | const collect = logInfo[0]; 43 | this.logField = { 44 | fetchConsumeTime: 0, 45 | level: '', 46 | message: '', 47 | requestTime: '', 48 | stack: '', 49 | status: '', 50 | timestamp: '', 51 | total: 0, 52 | requestBody: {}, 53 | method: '', 54 | url: '', 55 | }; 56 | if (typeof collect === 'string') { 57 | this.logField.message = collect; 58 | this.logField.requestTime = moment(Date.now()).format('YYYY-MM-DD HH:mm:ss'); 59 | this.logField.level = level; 60 | this.logField.stack = level === 'ERROR' ? logInfo[1] : ''; 61 | } else { 62 | this.logField.timestamp = collect.startTime; 63 | this.logField.requestTime = moment(collect.startTime).format('YYYY-MM-DD HH:mm:ss'); 64 | this.logField.status = collect.status; 65 | this.logField.message = collect.message; 66 | this.logField.stack = collect.stack; 67 | this.logField.level = collect.level || level; 68 | this.logField.url = collect.url; 69 | this.logField.total = collect.end - collect.start; 70 | if (collect.fetchStart && collect.fetchEnd) { 71 | this.logField.requestBody = collect.requestBody; 72 | this.logField.method = collect.method; 73 | this.logField.fetchConsumeTime = collect.fetchEnd - collect.fetchStart; 74 | } 75 | } 76 | return this.toBuffer(); 77 | } 78 | } 79 | 80 | export default class ElkTransport extends FileBufferTransport { 81 | 82 | log(this: Context, level: LoggerLevel, args: any) { 83 | const logFormat = new LogFormat(); 84 | let buf; 85 | if (!this._stream) { 86 | const err = new Error(`${this.options.file} log stream had been closed`); 87 | console.error(err.stack); 88 | return; 89 | } 90 | buf = logFormat.formatFetchInfoMsg(args, level); 91 | if (buf.length) { 92 | this._write(buf); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /client/src/components/XInput.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | {{text}} 6 | 7 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 50 | 51 | 52 | 131 | -------------------------------------------------------------------------------- /server/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /server/src/lib/baseSocketController.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg'; 2 | import { IGameRoom, IRoomInfo } from '../interface/IGameRoom'; 3 | import { IPlayer } from '../app/core/Player'; 4 | 5 | export default class BaseSocketController extends Controller { 6 | 7 | public app = this.ctx.app as any; 8 | public nsp = this.app.io.of('/socket'); 9 | public gameRooms = this.nsp.gameRooms; 10 | public socket = this.ctx.socket as any; 11 | public query = this.socket.handshake.query; 12 | public roomNumber = this.query.room; 13 | public jwt: any = this.app.jwt; 14 | public message = this.ctx.args[0] || {}; 15 | 16 | protected async getUserInfo() { 17 | const { token } = this.query; 18 | const user: IPlayer = this.jwt.verify(token) && this.jwt.verify(token).user; 19 | return user; 20 | } 21 | 22 | protected async getRoomInfo(): Promise { 23 | const { room } = this.query; 24 | const roomInfo = this.gameRooms.find((gr: IGameRoom) => gr.number === room); 25 | return roomInfo.roomInfo; 26 | } 27 | 28 | protected adapter(type: string, actionName: string, data: any) { 29 | return new Promise(resolve => { 30 | this.nsp.adapter.clients([ this.roomNumber ], (err: any, clients: any) => { 31 | this.nsp.to(this.roomNumber).emit(type, { 32 | clients, 33 | action: actionName, 34 | target: 'participator', 35 | data, 36 | }); 37 | resolve(); 38 | }); 39 | }); 40 | } 41 | 42 | protected async updateGameInfo() { 43 | const roomInfo = await this.getRoomInfo(); 44 | console.log(roomInfo, 'roomInfo ==============================='); 45 | if (roomInfo.game && roomInfo.game.status < 6 || (roomInfo.game?.status === 6 && roomInfo.game.playerSize === 1)) { 46 | roomInfo.players.forEach(p => { 47 | const currPlayer = roomInfo.game && 48 | roomInfo.game.getPlayers().find(player => player.userId === p.userId); 49 | p.counter = currPlayer?.counter || p.counter; 50 | p.type = currPlayer?.type || ''; 51 | p.status = currPlayer ? 1 : p.status === -1 ? -1 : 0; 52 | p.actionCommand = currPlayer && currPlayer.actionCommand || ''; 53 | p.delayCount = currPlayer && currPlayer.delayCount || 0; 54 | p.actionSize = currPlayer && currPlayer.actionSize || 0; 55 | }); 56 | console.log(roomInfo.players, 57 | 'roomInfo.players ===============================333'); 58 | const gameInfo = { 59 | players: roomInfo.players.map(p => { 60 | const currPlayer = roomInfo.game?.allPlayer.find( 61 | player => player.userId === p.userId); 62 | return Object.assign({}, { 63 | counter: currPlayer?.counter || p.counter, 64 | actionSize: currPlayer?.actionSize || 0, 65 | actionCommand: currPlayer?.actionCommand || '', 66 | nickName: p.nickName, 67 | type: currPlayer?.type || '', 68 | status: p.status || 0, 69 | userId: p.userId, 70 | buyIn: p.buyIn || 0, 71 | delayCount: currPlayer?.delayCount || 0, 72 | }, {}); 73 | }), 74 | pot: roomInfo.game.pot, 75 | prevSize: roomInfo.game.prevSize, 76 | sitList: roomInfo.sit, 77 | actionEndTime: roomInfo.game.actionEndTime, 78 | currPlayer: { 79 | userId: roomInfo.game.currPlayer.node.userId, 80 | }, 81 | smallBlind: roomInfo.config.smallBlind, 82 | }; 83 | console.log('gameInfo ==========', gameInfo); 84 | await this.adapter('online', 'gameInfo', gameInfo); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /client/src/views/register.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | J-POKER 5 | Create Account 6 | 7 | 11 | 12 | 13 | 17 | 18 | 19 | 24 | 25 | 26 | 31 | 32 | 33 | submit 34 | 35 | 36 | 37 | 38 | 95 | 119 | -------------------------------------------------------------------------------- /client/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /server/test/app/core/pokerStyle.test.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import {PokerStyle} from '../../../src/app/core/PokerStyle'; 3 | const assert = require('assert'); 4 | 5 | describe('test/app/core/pokerStyle.test.ts', () => { 6 | it('Royal Flush', async () => { 7 | let pokerStyle: PokerStyle = new PokerStyle(['i1','j1', 'k1', 'l1', 'm1', 'a2', 'a4']) 8 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 9 | assert.strictEqual(pokerStyle.getPokerWeight() , 'ijklm000000000') 10 | }); 11 | it('straight flush', async () => { 12 | let pokerStyle: PokerStyle = new PokerStyle(['a1','b1', 'c1', 'd1', 'e1', 'a2', 'a4']) 13 | // console.log(pokerStyle) 14 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 15 | assert.strictEqual(pokerStyle.getPokerWeight() , '0edcba00000000') 16 | }); 17 | it('four of kind', async () => { 18 | let pokerStyle: PokerStyle = new PokerStyle(['a1','b2', 'a3', 'b4', 'd1', 'a2', 'a4']) 19 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 20 | assert.strictEqual(pokerStyle.getPokerWeight() , '00ad0000000') 21 | }); 22 | // 23 | it('full house', async () => { 24 | let pokerStyle: PokerStyle = new PokerStyle(['a1','b2', 'c3', 'b4', 'd1', 'a2', 'b3']) 25 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 26 | assert.strictEqual(pokerStyle.getPokerWeight(), '000ba000000'); 27 | }); 28 | it('two full house', async () => { 29 | let pokerStyle: PokerStyle = new PokerStyle(['a1','b2', 'a3', 'b4', 'd1', 'a2', 'b3']) 30 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 31 | assert.strictEqual(pokerStyle.getPokerWeight() , '000ba000000') 32 | }); 33 | it('flush', async () => { 34 | let pokerStyle: PokerStyle = new PokerStyle(['m1','b1', 'k2', 'e1', 'f1', 'i1', 'k1']) 35 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 36 | assert.strictEqual(pokerStyle.getPokerWeight() , '0000mkife00000') 37 | }); 38 | it('straight', async () => { 39 | let pokerStyle: PokerStyle = new PokerStyle(['a1','c2', 'e3', 'b4', 'd1', 'g2', 'm3']) 40 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 41 | assert.strictEqual(pokerStyle.getPokerWeight() , '00000abcde0000') 42 | }); 43 | it('tow pairs to tow full house', async () => { 44 | let pokerStyle: PokerStyle = new PokerStyle(['a1','b2', 'a3', 'b4', 'd1', 'd2', 'b3']) 45 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 46 | assert.strictEqual(pokerStyle.getPokerWeight() , '000bd000000') 47 | }); 48 | it('tow pairs', async () => { 49 | let pokerStyle: PokerStyle = new PokerStyle(['a1','c2', 'd3', 'b4', 'f1', 'a2', 'c3']) 50 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 51 | assert.strictEqual(pokerStyle.getPokerWeight() , '0000000caf00') 52 | }); 53 | it('pairs', async () => { 54 | let pokerStyle: PokerStyle = new PokerStyle(['k1','c2', 'd3', 'b4', 'f1', 'a2', 'c3']) 55 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 56 | assert.strictEqual(pokerStyle.getPokerWeight() , '00000000ckfd0') 57 | }); 58 | it('high card', async () => { 59 | let pokerStyle: PokerStyle = new PokerStyle(['a1','i2', 'e3', 'b4', 'd1', 'g2', 'm3']) 60 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 61 | assert.strictEqual(pokerStyle.getPokerWeight() , '000000000miged') 62 | }); 63 | it('compare card', async () => { 64 | let pokerStyle: PokerStyle = new PokerStyle(['k2','k3','g2','e4','m3', 'm1', 'g1'], true) 65 | let pokerStyle2: PokerStyle = new PokerStyle(['k2','k3','g2','e4','m3','m2', 'c2'], true) 66 | console.log('getPokerValueCard', pokerStyle.getPokerValueCard()) 67 | assert.strictEqual(pokerStyle.getPokerWeight() , pokerStyle2.getPokerWeight()) 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /client/src/components/CommonCard.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 38 | 39 | 40 | 164 | -------------------------------------------------------------------------------- /server/src/app/controller/gameRecord.ts: -------------------------------------------------------------------------------- 1 | import { Context, inject, controller, post, provide } from 'midway'; 2 | import BaseController from '../../lib/baseController'; 3 | import { IPlayerService } from '../../interface/IPlayer'; 4 | import { 5 | ICommandRecord, 6 | ICommandRecordService, 7 | } from '../../interface/ICommandRecord'; 8 | import { IGameService } from '../../interface/IGame'; 9 | import { EGameOverType } from '../core/PokerGame'; 10 | 11 | interface IFindGameRecord { 12 | gameId: number; 13 | winners: string; 14 | commandList: ICommandRecord []; 15 | } 16 | 17 | @provide() 18 | @controller('/node/game/record') 19 | export class GameRecordController extends BaseController { 20 | 21 | @inject() 22 | ctx: Context; 23 | 24 | @inject('PlayerRecordService') 25 | playerService: IPlayerService; 26 | 27 | @inject('GameService') 28 | gameService: IGameService; 29 | 30 | @inject('CommandRecordService') 31 | commandService: ICommandRecordService; 32 | 33 | @post('/find/commandRecord') 34 | async find() { 35 | try { 36 | const { body } = this.getRequestBody(); 37 | const state = this.ctx.state; 38 | const commandList = await this.commandService.findByGameID(body.gameId); 39 | const gameList = await this.gameService.findByRoomNumber(body.roomNumber); 40 | let result: IFindGameRecord; 41 | console.log(state, 'user'); 42 | gameList.forEach(g => { 43 | if (g.status === EGameOverType.GAME_OVER) { 44 | const winner = JSON.parse(g.winners || '')[0][0]; 45 | delete winner.handCard; 46 | g.winners = JSON.stringify([[ winner ]]); 47 | } 48 | }); 49 | commandList.forEach(c => { 50 | if (c.userId !== state.user.user.userId) { 51 | c.handCard = ''; 52 | } 53 | }); 54 | result = { 55 | commandList, 56 | winners: gameList.find(g => g.id === body.gameId)?.winners || '', 57 | gameId: body.gameId, 58 | }; 59 | this.success({ 60 | ...result, 61 | }); 62 | } catch (e) { 63 | this.fail('invalid game record'); 64 | console.log(e); 65 | } 66 | } 67 | 68 | @post('/find/selfPast7DayGame') 69 | async selfPast7DayGame() { 70 | try { 71 | const { body } = this.getRequestBody(); 72 | const gameIDList = await this.commandService.findPast7DayGameIDsByUserID(body.userID); 73 | 74 | if (!gameIDList.length) { 75 | this.success([]); 76 | return; 77 | } 78 | const gameList = await this.gameService.findByIDs(gameIDList); 79 | const commandList = await this.commandService.findByGameIDs(gameIDList); 80 | 81 | const result: any = []; 82 | gameList.forEach(g => { 83 | if (g.status === EGameOverType.GAME_OVER) { 84 | const winner = JSON.parse(g.winners || '')[0][0]; 85 | delete winner.handCard; 86 | g.winners = JSON.stringify([[ winner ]]); 87 | } 88 | 89 | const gameCommandList = commandList.filter(c => { 90 | return c.gameId === g.id; 91 | }); 92 | 93 | // 过滤其他人手牌 94 | gameCommandList.forEach(c => { 95 | if (c.userId !== this.ctx.state.user.user.userId) { 96 | c.handCard = ''; 97 | } 98 | }); 99 | 100 | result.push({ 101 | gameCommandList, 102 | winners: g.winners, 103 | gameId: g.id, 104 | }); 105 | }); 106 | this.success(result); 107 | } catch (e) { 108 | this.fail('find self command record error'); 109 | console.log(e); 110 | } 111 | } 112 | 113 | @post('/find/gameRecord') 114 | async index() { 115 | try { 116 | const { body } = this.getRequestBody(); 117 | const gameList = await this.gameService.findByRoomNumber(body.roomNumber); 118 | const result = gameList.map(g => Object.assign({}, {}, { gameId: g.id })); 119 | this.success({ 120 | ...result, 121 | }); 122 | } catch (e) { 123 | this.fail('create room error'); 124 | console.log(e); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /database/poker.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Source Server : test11 5 | Source Server Version : 50639 6 | Source Host : 47.104.172.100:3306 7 | Source Database : poker 8 | 9 | Target Server Type : MYSQL 10 | Target Server Version : 50639 11 | File Encoding : 65001 12 | 13 | Date: 2020-07-08 15:11:08 14 | */ 15 | 16 | SET FOREIGN_KEY_CHECKS=0; 17 | 18 | -- ---------------------------- 19 | -- Table structure for command_record 20 | -- ---------------------------- 21 | DROP TABLE IF EXISTS `command_record`; 22 | CREATE TABLE `command_record` ( 23 | `id` int(11) NOT NULL AUTO_INCREMENT, 24 | `userId` int(11) DEFAULT NULL, 25 | `gameId` int(11) DEFAULT NULL, 26 | `type` text, 27 | `gameStatus` int(11) DEFAULT NULL, 28 | `counter` int(11) DEFAULT NULL, 29 | `command` text, 30 | `commonCard` text, 31 | `pot` int(11) DEFAULT NULL, 32 | `roomNumber` int(11) DEFAULT NULL, 33 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 34 | `update_time` datetime DEFAULT NULL, 35 | PRIMARY KEY (`id`) 36 | ) ENGINE=InnoDB AUTO_INCREMENT=26259 DEFAULT CHARSET=latin1; 37 | 38 | -- ---------------------------- 39 | -- Table structure for game 40 | -- ---------------------------- 41 | DROP TABLE IF EXISTS `game`; 42 | CREATE TABLE `game` ( 43 | `id` int(11) NOT NULL AUTO_INCREMENT, 44 | `roomNumber` int(11) DEFAULT NULL, 45 | `status` int(11) DEFAULT NULL, 46 | `commonCard` text, 47 | `winners` text CHARACTER SET utf8, 48 | `pot` decimal(8,0) DEFAULT NULL, 49 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 50 | `update_time` datetime DEFAULT NULL, 51 | PRIMARY KEY (`id`) 52 | ) ENGINE=InnoDB AUTO_INCREMENT=2546 DEFAULT CHARSET=latin1; 53 | 54 | -- ---------------------------- 55 | -- Table structure for player 56 | -- ---------------------------- 57 | DROP TABLE IF EXISTS `player`; 58 | CREATE TABLE `player` ( 59 | `id` int(11) NOT NULL AUTO_INCREMENT, 60 | `gameId` int(11) DEFAULT NULL, 61 | `roomNumber` int(11) DEFAULT NULL, 62 | `buyIn` int(11) NOT NULL, 63 | `handCard` varchar(25) DEFAULT NULL, 64 | `counter` int(11) DEFAULT NULL, 65 | `userId` int(11) DEFAULT NULL, 66 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 67 | `update_time` datetime DEFAULT NULL, 68 | PRIMARY KEY (`id`) 69 | ) ENGINE=InnoDB AUTO_INCREMENT=7440 DEFAULT CHARSET=latin1; 70 | 71 | -- ---------------------------- 72 | -- Table structure for room 73 | -- ---------------------------- 74 | DROP TABLE IF EXISTS `room`; 75 | CREATE TABLE `room` ( 76 | `id` int(11) NOT NULL AUTO_INCREMENT, 77 | `smallBlind` int(11) DEFAULT NULL, 78 | `isShort` int(11) DEFAULT NULL, 79 | `time` int(11) DEFAULT NULL, 80 | `roomNumber` text CHARACTER SET latin1, 81 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 82 | `update_time` datetime DEFAULT NULL, 83 | PRIMARY KEY (`id`) 84 | ) ENGINE=InnoDB AUTO_INCREMENT=351 DEFAULT CHARSET=utf8; 85 | 86 | -- ---------------------------- 87 | -- Table structure for user 88 | -- ---------------------------- 89 | DROP TABLE IF EXISTS `user`; 90 | CREATE TABLE `user` ( 91 | `id` int(11) NOT NULL AUTO_INCREMENT, 92 | `nickName` char(25) CHARACTER SET utf8 DEFAULT NULL, 93 | `password` char(25) DEFAULT NULL, 94 | `account` char(25) CHARACTER SET utf8 DEFAULT NULL, 95 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 96 | `update_time` datetime DEFAULT NULL, 97 | PRIMARY KEY (`id`) 98 | ) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=latin1; 99 | DROP TRIGGER IF EXISTS `update_comand_record_time`; 100 | DELIMITER ;; 101 | CREATE TRIGGER `update_comand_record_time` BEFORE UPDATE ON `command_record` FOR EACH ROW SET NEW.`UPDATE_TIME` = NOW() 102 | ;; 103 | DELIMITER ; 104 | DROP TRIGGER IF EXISTS `update_game_time`; 105 | DELIMITER ;; 106 | CREATE TRIGGER `update_game_time` BEFORE UPDATE ON `game` FOR EACH ROW SET NEW.`UPDATE_TIME` = NOW() 107 | ;; 108 | DELIMITER ; 109 | DROP TRIGGER IF EXISTS `update_game_record_time`; 110 | DELIMITER ;; 111 | CREATE TRIGGER `update_game_record_time` BEFORE UPDATE ON `player` FOR EACH ROW SET NEW.`UPDATE_TIME` = NOW() 112 | ;; 113 | DELIMITER ; 114 | DROP TRIGGER IF EXISTS `update_user_time`; 115 | DELIMITER ;; 116 | CREATE TRIGGER `update_user_time` BEFORE UPDATE ON `user` FOR EACH ROW SET NEW.`UPDATE_TIME` = NOW() 117 | ;; 118 | DELIMITER ; 119 | 120 | 121 | /* 2022.10.16 command record 添加 user id 和 game id 索引 */ 122 | ALTER TABLE `command_record` ADD INDEX `idx_user_id`(`userId`); 123 | ALTER TABLE `command_record` ADD INDEX `idx_game_id`(`gameId`); 124 | ALTER TABLE `player` ADD INDEX `idx_user_id`(`userId`); 125 | 126 | -------------------------------------------------------------------------------- /server/src/app/core/Player.ts: -------------------------------------------------------------------------------- 1 | export interface IPlayer { 2 | counter: number; 3 | buyIn: number; 4 | position?: number; 5 | userId: string; 6 | nickName: string; 7 | account: string; 8 | socketId: string; 9 | income?: number; 10 | type: string; 11 | reBuy: number; 12 | status: number; 13 | actionSize: number; 14 | actionCommand: string; 15 | delayCount?: number; 16 | id?: number; 17 | } 18 | 19 | export enum ECommand { 20 | SMALL_BLIND = 'sb', 21 | BIG_BLIND = 'bb', 22 | STRADDLE = 'straddle', 23 | CALL = 'call', 24 | ALL_IN = 'allin', 25 | RAISE = 'raise', 26 | CHECK = 'check', 27 | FOLD = 'fold', 28 | } 29 | 30 | export enum EPlayerType { 31 | DEFAULT = '', 32 | DEALER = 'd', 33 | BIG_BLIND = 'bb', 34 | SMALL_BLIND = 'sb', 35 | } 36 | 37 | export class Player { 38 | private handCard: string[] = []; 39 | position: number = 0; 40 | counter: number = 0; 41 | userId: string = ''; 42 | playerId: number = 0; 43 | delayCount: number = 3; 44 | socketId: string = ''; 45 | nickName: string = ''; 46 | actionSize: number = 0; 47 | actionCommand: string = ''; 48 | type: string = EPlayerType.DEFAULT; 49 | evPot: number = Infinity; 50 | inPot: number = 0; 51 | income: number = 0; 52 | pokerStyle: string = ''; 53 | 54 | constructor(config: IPlayer) { 55 | this.counter = config.counter; 56 | this.position = config.position || 0; 57 | this.userId = config.userId; 58 | this.socketId = config.socketId; 59 | this.nickName = config.nickName; 60 | if (this.position === 0) { 61 | this.type = EPlayerType.DEALER; 62 | } 63 | if (this.position === 1) { 64 | this.type = EPlayerType.SMALL_BLIND; 65 | } 66 | if (this.position === 2) { 67 | this.type = EPlayerType.BIG_BLIND; 68 | } 69 | } 70 | 71 | setHandCard(card: string) { 72 | this.handCard.push(card); 73 | } 74 | 75 | getHandCard() { 76 | return this.handCard; 77 | } 78 | 79 | /** 80 | * player action 81 | * @param {string} commandString - player action command string 82 | * @param {number} prevSize - prev player action size 83 | * @example action('command:raise:10') 84 | */ 85 | action(commandString: string, prevSize: number = 0) { 86 | const commandArr = commandString.split(':'); 87 | const command = commandArr[0]; 88 | const raiseSize = Number(commandArr[1]); 89 | let size = 0; 90 | if ((command !== ECommand.ALL_IN && command !== ECommand.FOLD) 91 | && (prevSize > (this.counter + this.actionSize) || raiseSize > this.counter)) { 92 | throw 'player: error action, overflow action size'; 93 | } else { 94 | this.actionCommand = (command === ECommand.SMALL_BLIND || command === ECommand.BIG_BLIND) ? '' : command; 95 | } 96 | 97 | // BLIND 98 | if (command === ECommand.SMALL_BLIND || command === ECommand.BIG_BLIND) { 99 | size = raiseSize; 100 | } 101 | 102 | // todo STRADDLE 103 | if (command === ECommand.STRADDLE) { 104 | // position 0 is dealer 105 | if (this.position === 3) { 106 | size = raiseSize; 107 | } else { 108 | throw 'player: error action STRADDLE'; 109 | } 110 | } 111 | 112 | // player raise,get the raise size 113 | if (command === ECommand.RAISE) { 114 | // raise must double to prevSize 115 | if (raiseSize >= prevSize * 2) { 116 | console.log('player: RAISE----------------', prevSize, this.actionSize); 117 | const actionSize = this.actionSize >= 0 ? this.actionSize : 0; 118 | size = raiseSize - actionSize; 119 | } else { 120 | throw 'player: error action: raise size too small'; 121 | } 122 | } 123 | 124 | if (command === ECommand.ALL_IN) { 125 | console.log('allin================', this.counter); 126 | size = this.counter; 127 | } 128 | 129 | if (command === ECommand.CALL) { 130 | console.log('player: call----------------', prevSize, this.actionSize); 131 | const actionSize = this.actionSize >= 0 ? this.actionSize : 0; 132 | size = prevSize - actionSize; 133 | } 134 | 135 | if (command === ECommand.CHECK) { 136 | size = -1; 137 | } 138 | 139 | if (command === ECommand.FOLD) { 140 | size = 0; 141 | } 142 | if (size > 0) { 143 | this.counter -= size; 144 | this.inPot += size; 145 | } 146 | console.log('allin================', this.counter); 147 | this.actionSize += size; 148 | if (command === ECommand.RAISE) { 149 | this.actionSize = raiseSize; 150 | } else if (command === ECommand.CALL) { 151 | this.actionSize = prevSize; 152 | } 153 | return size; 154 | } 155 | 156 | clearActionSize() { 157 | this.actionSize = 0; 158 | if (this.actionCommand !== 'fold' && this.actionCommand !== 'allin') { 159 | this.actionCommand = ''; 160 | } 161 | } 162 | 163 | setIncome(size: number) { 164 | console.log('size', size); 165 | this.income = size; 166 | this.counter += size; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /client/src/components/Player.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | {{ player.nickName }} 10 | 11 | 12 | 14 | {{ player.counter }} 15 | 16 | 18 | {{ player.actionSize }} 19 | 20 | 22 | {{ player.command }} 23 | 24 | 26 | {{ player.type }} 27 | 28 | 30 | 31 | 32 | 33 | 35 | {{PokeStyle(player.handCard, player.commonCard)}} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 68 | 69 | 70 | 177 | -------------------------------------------------------------------------------- /client/src/components/CardList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 11 | 13 | {{ map(card)[0] }} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 59 | 60 | 61 | 201 | -------------------------------------------------------------------------------- /server/src/app/io/middleware/join.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'midway'; 2 | import { IGameRoom } from '../../../interface/IGameRoom'; 3 | import { IPlayer } from '../../core/Player'; 4 | 5 | export default function join(): any { 6 | function updatePlayer(roomNumber: string, players: any, action: string, nsp: any) { 7 | // 在线列表 8 | nsp.adapter.clients([ roomNumber ], (err: any, clients: any) => { 9 | // 更新在线用户列表 10 | nsp.to(roomNumber).emit('online', { 11 | clients, 12 | action, 13 | target: 'participator', 14 | data: { 15 | players, 16 | }, 17 | }); 18 | }); 19 | } 20 | return async (ctx: Context, next: () => Promise) => { 21 | const socket = ctx.socket as any; 22 | const id = socket.id; 23 | const app = ctx.app as any; 24 | const nsp = app.io.of('/socket'); 25 | const query = socket.handshake.query; 26 | const { room, token, roomConfig } = query; 27 | console.log('socket-----join', id); 28 | console.log('roomConfig-----roomConfig', JSON.parse(roomConfig)); 29 | // room缓存信息是否存在 30 | if (!nsp.gameRooms) { 31 | nsp.gameRooms = []; 32 | } 33 | try { 34 | const hasRoom = nsp.gameRooms.find((r: IGameRoom) => r.number === room); 35 | const { user } = await app.jwt.verify(token); 36 | socket.join(room); 37 | await socket.emit(id, ctx.helper.parseMsg('userInfo', { userInfo: user })); 38 | const player: IPlayer = { 39 | ...user, 40 | socketId: id, 41 | counter: 0, 42 | buyIn: 0, 43 | delayCount: 3, 44 | reBuy: 0, 45 | }; 46 | let gameRoom: IGameRoom = { 47 | number: room, 48 | roomInfo: { 49 | sit: [], 50 | players: [], 51 | game: null, 52 | sitLink: null, 53 | config: JSON.parse(roomConfig) || { 54 | isShort: false, 55 | smallBlind: 1, 56 | }, 57 | }, 58 | }; 59 | if (!hasRoom) { 60 | // not in the room 61 | nsp.gameRooms.push(gameRoom); 62 | gameRoom.roomInfo = { 63 | sit: [], 64 | players: [ player ], 65 | game: null, 66 | sitLink: null, 67 | config: JSON.parse(roomConfig) || { 68 | isShort: false, 69 | smallBlind: 1, 70 | }, 71 | }; 72 | updatePlayer(room, gameRoom.roomInfo.players, 'players', nsp); 73 | } else { 74 | // in the room 75 | gameRoom = nsp.gameRooms.find((r: IGameRoom) => r.number === room); 76 | const findPlayer = gameRoom.roomInfo.players.find((p: IPlayer) => p.userId === user.userId); 77 | if (!findPlayer) { 78 | // game ready 79 | gameRoom.roomInfo.players.push(player); 80 | updatePlayer(room, gameRoom.roomInfo.players, 'players', nsp); 81 | } else { 82 | // gaming, update hand cards 83 | findPlayer.socketId = id; 84 | const gamePlayer = gameRoom.roomInfo.game?.allPlayer.find(p => user.userId === p.userId); 85 | if (gamePlayer) { 86 | // in the game, get hand card 87 | const msg = ctx.helper.parseMsg('handCard', { 88 | handCard: gamePlayer.getHandCard(), 89 | }, { client: id }); 90 | socket.emit(id, msg); 91 | } 92 | if (gameRoom.roomInfo) { 93 | const roomInfo = gameRoom.roomInfo; 94 | const gameInfo = { 95 | players: roomInfo.players.map(p => { 96 | const currPlayer = roomInfo.game?.allPlayer.find(player => player.userId === p.userId); 97 | console.log('currPlayer ========== ', currPlayer); 98 | return Object.assign({}, { 99 | counter: currPlayer?.counter || p.counter, 100 | actionSize: currPlayer?.actionSize || 0, 101 | actionCommand: currPlayer?.actionCommand || '', 102 | nickName: p.nickName, 103 | type: currPlayer?.type || '', 104 | userId: p.userId, 105 | status: p.status, 106 | buyIn: p.buyIn || 0, 107 | }, {}); 108 | }), 109 | commonCard: roomInfo.game?.commonCard || [], 110 | pot: roomInfo.game?.pot || 0, 111 | prevSize: roomInfo.game?.prevSize || 0, 112 | currPlayer: { 113 | userId: roomInfo.game?.currPlayer.node.userId, 114 | }, 115 | smallBlind: roomInfo.config.smallBlind, 116 | actionEndTime: roomInfo.game?.actionEndTime || 0, 117 | }; 118 | const game = ctx.helper.parseMsg('gameInfo', { 119 | data: gameInfo, 120 | }, { client: id }); 121 | socket.emit(id, game); 122 | } 123 | } 124 | // get sitList 125 | const msg = ctx.helper.parseMsg('sitList', { 126 | sitList: gameRoom.roomInfo.sit, 127 | }, { client: id }); 128 | socket.emit(id, msg); 129 | } 130 | // console.log('players', JSON.stringify(gameRoom.roomInfo.players)); 131 | updatePlayer(room, `User(${user.nickName}) joined.`, 'join', nsp); 132 | await next(); 133 | } catch (e) { 134 | throw e; 135 | } 136 | }; 137 | } 138 | -------------------------------------------------------------------------------- /client/src/components/GameRecord.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | record ({{gameList[currGameIndex - 1].gameId}}) 7 | 8 | 9 | 10 | player 11 | commonCard 12 | pot 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | {{player.pot}} 27 | 28 | 29 | 30 | 31 | 32 | {{currGameIndex}} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 93 | 94 | 95 | 241 | -------------------------------------------------------------------------------- /server/src/app/core/PokerStyle.ts: -------------------------------------------------------------------------------- 1 | const POKER_STR = 'abcdefghijklm'; 2 | 3 | function sort(cards: string []): string[] { 4 | let temp = ''; 5 | // 排序 6 | for (let i = 0; i < cards.length; i++) { 7 | for (let j = i + 1; j < cards.length; j++) { 8 | if (cards[i] > cards[j]) { 9 | temp = cards[i]; 10 | cards[i] = cards[j]; 11 | cards[j] = temp; 12 | } 13 | } 14 | } 15 | return cards; 16 | } 17 | 18 | interface IPokerStyle { 19 | init(): void; 20 | isStraight(str?: string []): string; 21 | } 22 | 23 | export class PokerStyle implements IPokerStyle { 24 | cards: string[] = []; 25 | flushObj = { 26 | 1: [] as string[], 27 | 2: [] as string[], 28 | 3: [] as string[], 29 | 4: [] as string[], 30 | }; 31 | flushColor: string = ''; 32 | isShort: boolean; 33 | straightArr: string[] = []; 34 | pokerStyle: string[] = [ '0', '0', '0', '0', '0', '0', '0', '0', '0', '0' ]; 35 | numObj: Map = new Map( 36 | POKER_STR.split('').map(m => [ m, 0 ])); 37 | 38 | constructor(cards: string[], isShort= false) { 39 | this.cards = sort(cards); 40 | this.isShort = isShort; 41 | this.init(); 42 | } 43 | 44 | init() { 45 | let i = 0; 46 | const isTwo = []; 47 | const isThree = []; 48 | let isFour = '0'; 49 | let isFullHouse = '0'; 50 | // const isStraightFlush = '0'; 51 | let isFlush: string[] = []; 52 | let isRoyalFlush = '0'; 53 | let isThreeKind = ''; 54 | let isTowPair = ''; 55 | let isPair = ''; 56 | const highCard = []; 57 | 58 | while (i < this.cards.length) { 59 | const color = this.cards[i][1]; 60 | const num = this.cards[i][0]; 61 | this.straightArr.push(this.cards[i][0]); 62 | this.flushObj[color].push(num); 63 | let value = this.numObj.get(num) || 0; 64 | value++; 65 | this.numObj.set(num, value); 66 | i++; 67 | } 68 | 69 | // find flush 70 | for (const f in this.flushObj) { 71 | if (this.flushObj[f].length >= 5) { 72 | // flush is order,so flush[length - 1] is max flush card 73 | isFlush = this.flushObj[f]; 74 | this.flushColor = f; 75 | } 76 | } 77 | 78 | // find two,three,four 79 | for (const [ key, value ] of this.numObj) { 80 | // high card 81 | if (value === 1) { 82 | highCard.unshift(key); 83 | } 84 | // pairs max count 3, source is small to large 85 | if (value >= 2) { 86 | isTwo.unshift(key); 87 | } 88 | // three of kind max count 2 89 | if (value === 3) { 90 | isThree.unshift(key); 91 | } 92 | // four of kind only one 93 | if (value === 4) { 94 | isFour = key; 95 | } 96 | } 97 | // straight flush 98 | if (isFlush.length !== 0 && this.isStraight(isFlush) !== '0') { 99 | if (this.isStraight(isFlush) === 'ijklm') { 100 | isRoyalFlush = 'ijklm'; 101 | this.pokerStyle[0] = isRoyalFlush; 102 | return; 103 | } 104 | this.pokerStyle[1] = this.isStraight(isFlush).split('').reverse().join(''); 105 | return; 106 | } 107 | 108 | // four of kind 109 | if (isFour !== '0') { 110 | isFour += highCard[0]; 111 | this.pokerStyle[2] = isFour; 112 | return; 113 | } 114 | 115 | // full house 116 | if (isThree.length > 0 && isTwo.length > isThree.length || isThree.length === 2) { 117 | const maxTwoCard = isThree.length === 2 ? isThree[1] : isThree[0] === isTwo[0] ? isTwo[1] : isTwo[0]; 118 | const maxThree = isThree[0]; 119 | isFullHouse = maxThree + maxTwoCard; 120 | if (this.isShort) { 121 | this.pokerStyle[4] = isFullHouse; 122 | } else { 123 | this.pokerStyle[3] = isFullHouse; 124 | } 125 | return; 126 | } 127 | 128 | // flush 129 | if (isFlush.length !== 0) { 130 | isFlush.reverse().length = 5; 131 | if (this.isShort) { 132 | this.pokerStyle[3] = isFlush.join(''); 133 | } else { 134 | this.pokerStyle[4] = isFlush.join(''); 135 | } 136 | return; 137 | } 138 | 139 | console.log('come in -------', isThree); 140 | // straight 141 | if (this.isStraight() !== '0') { 142 | this.pokerStyle[5] = `${this.isStraight()}`; 143 | return; 144 | } 145 | // three of kind 146 | if (isThree.length > 0) { 147 | isThreeKind = isThree.join(''); 148 | isThreeKind += highCard[0] + highCard[1]; 149 | this.pokerStyle[6] = isThreeKind; 150 | return; 151 | } 152 | 153 | // tow pair 154 | if (isTwo.length >= 2) { 155 | const towPair = isTwo; 156 | const threeTowPair = towPair[2] || ''; 157 | towPair.length = 2; 158 | isTowPair = towPair.join(''); 159 | // third tow pair card big then high card 160 | const highCardForTowPair = threeTowPair > highCard[0] ? threeTowPair : highCard[0]; 161 | isTowPair += highCardForTowPair; 162 | this.pokerStyle[7] = isTowPair; 163 | return; 164 | } 165 | // pair 166 | if (isTwo.length === 1) { 167 | isPair = isTwo.join(''); 168 | isPair += highCard[0] + highCard[1] + highCard[2]; 169 | this.pokerStyle[8] = isPair; 170 | return; 171 | } 172 | // High card 173 | highCard.length = 5; 174 | this.pokerStyle[9] = highCard.join(''); 175 | } 176 | 177 | isStraight(str?: string []): string { 178 | const straightStr = str && str.join('') || [ ...new Set(this.straightArr) ].join(''); 179 | let first = -1; 180 | let second = -1; 181 | let three = -1; 182 | function indexOf(str: string): number { 183 | return POKER_STR.indexOf(str); 184 | } 185 | if (straightStr.length === 5 && indexOf(straightStr) > -1) { 186 | return POKER_STR.slice(indexOf(straightStr), indexOf(straightStr) + 5); 187 | } 188 | if (straightStr.length === 6) { 189 | first = indexOf(straightStr.slice(0, 5)); 190 | second = indexOf(straightStr.slice(1, 6)); 191 | if (Math.max(first, second) > -1) { 192 | const max = Math.max(first, second); 193 | return POKER_STR.slice(max, max + 5); 194 | } 195 | } 196 | if (straightStr.length === 7) { 197 | first = indexOf(straightStr.slice(0, 5)); 198 | second = indexOf(straightStr.slice(1, 6)); 199 | three = indexOf(straightStr.slice(2, 7)); 200 | if (Math.max(first, second, three) > -1) { 201 | const max = Math.max(first, second, three); 202 | return POKER_STR.slice(max, max + 5); 203 | } 204 | } 205 | // special straight "A2345",'m' -> A 206 | if (!this.isShort && straightStr.indexOf('m') > -1 && straightStr.indexOf('abcd') > -1) { 207 | return 'abcd'; 208 | } 209 | // special straight "A2345",'m' -> A 210 | if (this.isShort && straightStr.indexOf('m') > -1 && straightStr.indexOf('efgh') > -1) { 211 | return 'efgh'; 212 | } 213 | return '0'; 214 | } 215 | 216 | getPokerWeight() { 217 | return this.pokerStyle.join(''); 218 | } 219 | 220 | getPokerValueCard() { 221 | let valueStyle = ''; 222 | let isFlush = false; 223 | this.pokerStyle.forEach((style, key) => { 224 | if (style !== '0') { 225 | isFlush = key === 1 || this.isShort ? key === 3 : key === 4; 226 | valueStyle = style; 227 | } 228 | }); 229 | const cards = this.cards.filter(card => { 230 | if (isFlush) { 231 | return valueStyle.indexOf(card[0]) > -1 && card[1] === this.flushColor; 232 | } 233 | return valueStyle.indexOf(card[0]) > -1; 234 | }); 235 | cards.reverse().length = 5; 236 | return cards; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /client/src/utils/PokerStyle.ts: -------------------------------------------------------------------------------- 1 | const POKER_STR = 'abcdefghijklm'; 2 | 3 | function sort(cards: string []): string[] { 4 | let temp = ''; 5 | // 排序 6 | for (let i = 0; i < cards.length; i++) { 7 | for (let j = i + 1; j < cards.length; j++) { 8 | if (cards[i] > cards[j]) { 9 | temp = cards[i]; 10 | cards[i] = cards[j]; 11 | cards[j] = temp; 12 | } 13 | } 14 | } 15 | return cards; 16 | } 17 | 18 | interface IPokerStyle { 19 | init(): void; 20 | 21 | isStraight(str?: string []): string; 22 | } 23 | 24 | enum PokerStyleEnum { 25 | 'ROYAL_FlUSH', 26 | 'STRAIGHT_FLUSH', 27 | 'FOUR_KIND', 28 | 'FULL_HOUSE', 29 | 'FLUSH', 30 | 'STRAIGHT', 31 | 'THREE_KIND', 32 | 'TWO_PAIR', 33 | 'PAIR', 34 | 'HIGH_CARD', 35 | } 36 | 37 | enum ShortPokerStyleEnum { 38 | 'ROYAL_FlUSH', 39 | 'STRAIGHT_FLUSH', 40 | 'FOUR_KIND', 41 | 'FLUSH', 42 | 'FULL_HOUSE', 43 | 'STRAIGHT', 44 | 'THREE_KIND', 45 | 'TWO_PAIR', 46 | 'PAIR', 47 | 'HIGH_CARD', 48 | } 49 | 50 | export class PokerStyle implements IPokerStyle { 51 | private readonly cards: string[] = []; 52 | private readonly isShort: boolean; 53 | private flushObj: { [key: string]: any } = { 54 | 1: [], 55 | 2: [], 56 | 3: [], 57 | 4: [], 58 | }; 59 | private flushColor: string = ''; 60 | private straightArr: string[] = []; 61 | private styleName = [ 62 | 'ROYAL_FlUSH', 63 | 'STRAIGHT_FLUSH', 64 | 'FOUR_KIND', 65 | 'FULL_HOUSE', 66 | 'FLUSH', 67 | 'STRAIGHT', 68 | 'THREE_KIND', 69 | 'TWO_PAIR', 70 | 'PAIR', 71 | 'HIGH_CARD']; 72 | private pokerStyle: string[] = [ 73 | '0', 74 | '0', 75 | '0', 76 | '0', 77 | '0', 78 | '0', 79 | '0', 80 | '0', 81 | '0', 82 | '0']; 83 | private numObj: Map = new Map( 84 | POKER_STR.split('').map((m) => [m, 0])); 85 | 86 | constructor(cards: string[], isShort= false) { 87 | this.cards = sort(cards); 88 | this.isShort = isShort; 89 | if (this.isShort) { 90 | this.styleName = [ 91 | 'ROYAL_FlUSH', 92 | 'STRAIGHT_FLUSH', 93 | 'FOUR_KIND', 94 | 'FLUSH', 95 | 'FULL_HOUSE', 96 | 'STRAIGHT', 97 | 'THREE_KIND', 98 | 'TWO_PAIR', 99 | 'PAIR', 100 | 'HIGH_CARD', 101 | ]; 102 | } 103 | this.init(); 104 | } 105 | 106 | public isStraight(str?: string []): string { 107 | const straightStr = str && str.join('') || [ ...new Set(this.straightArr) ].join(''); 108 | let first = -1; 109 | let second = -1; 110 | let three = -1; 111 | function indexOf(pokeString: string): number { 112 | return POKER_STR.indexOf(pokeString); 113 | } 114 | if (straightStr.length === 5 && indexOf(straightStr) > -1) { 115 | return POKER_STR.slice(indexOf(straightStr), indexOf(straightStr) + 5); 116 | } 117 | if (straightStr.length === 6) { 118 | first = indexOf(straightStr.slice(0, 5)); 119 | second = indexOf(straightStr.slice(1, 6)); 120 | if (Math.max(first, second) > -1) { 121 | const max = Math.max(first, second); 122 | return POKER_STR.slice(max, max + 5); 123 | } 124 | } 125 | if (straightStr.length === 7) { 126 | first = indexOf(straightStr.slice(0, 5)); 127 | second = indexOf(straightStr.slice(1, 6)); 128 | three = indexOf(straightStr.slice(2, 7)); 129 | if (Math.max(first, second, three) > -1) { 130 | const max = Math.max(first, second, three); 131 | return POKER_STR.slice(max, max + 5); 132 | } 133 | } 134 | // special straight "A2345",'m' -> A 135 | if (!this.isShort && straightStr.indexOf('m') > -1 && straightStr.indexOf('abcd') > -1) { 136 | return 'abcdm'; 137 | } 138 | // special straight "A2345",'m' -> A 139 | if (this.isShort && straightStr.indexOf('m') > -1 && straightStr.indexOf('efgh') > -1) { 140 | return 'efghm'; 141 | } 142 | return '0'; 143 | } 144 | 145 | public getPokerWeight() { 146 | return this.pokerStyle.join(''); 147 | } 148 | 149 | public getPokerStyleName() { 150 | for (let i = 0; i < this.pokerStyle.length; i++) { 151 | if (this.pokerStyle[i] !== '0') { 152 | return this.styleName[i]; 153 | } 154 | } 155 | } 156 | 157 | public init() { 158 | let i = 0; 159 | const isTwo = []; 160 | const isThree = []; 161 | let isFour = '0'; 162 | let isFullHouse = '0'; 163 | // const isStraightFlush = '0'; 164 | let isFlush: string[] = []; 165 | let isRoyalFlush = '0'; 166 | let isThreeKind = ''; 167 | let isTowPair = ''; 168 | let isPair = ''; 169 | const highCard = []; 170 | 171 | while (i < this.cards.length) { 172 | const color = this.cards[i][1]; 173 | const num = this.cards[i][0]; 174 | this.straightArr.push(this.cards[i][0]); 175 | this.flushObj[color].push(num); 176 | let value = this.numObj.get(num) || 0; 177 | value++; 178 | this.numObj.set(num, value); 179 | i++; 180 | } 181 | 182 | // find flush 183 | for (const f in this.flushObj) { 184 | if (this.flushObj[f].length >= 5) { 185 | // flush is order,so flush[length - 1] is max flush card 186 | isFlush = this.flushObj[f]; 187 | this.flushColor = f; 188 | } 189 | } 190 | 191 | // find two,three,four 192 | for (const [key, value] of this.numObj) { 193 | // high card 194 | if (value === 1) { 195 | highCard.unshift(key); 196 | } 197 | // pairs max count 3, source is small to large 198 | if (value >= 2) { 199 | isTwo.unshift(key); 200 | } 201 | // three of kind max count 2 202 | if (value === 3) { 203 | isThree.unshift(key); 204 | } 205 | // four of kind only one 206 | if (value === 4) { 207 | isFour = key; 208 | } 209 | } 210 | // straight flush 211 | if (isFlush.length !== 0 && this.isStraight(isFlush) !== '0') { 212 | if (this.isStraight(isFlush) === 'ijklm') { 213 | isRoyalFlush = 'ijklm'; 214 | this.pokerStyle[0] = isRoyalFlush; 215 | return; 216 | } 217 | this.pokerStyle[1] = this.isStraight(isFlush).split('').reverse().join(''); 218 | return; 219 | } 220 | 221 | // four of kind 222 | if (isFour !== '0') { 223 | isFour += highCard[0]; 224 | this.pokerStyle[2] = isFour; 225 | return; 226 | } 227 | 228 | // full house 229 | if (isThree.length > 0 && isTwo.length > isThree.length || 230 | isThree.length === 2) { 231 | const maxTwoCard = isThree.length === 2 ? isThree[1] : isThree[0] === 232 | isTwo[0] ? isTwo[1] : isTwo[0]; 233 | const maxThree = isThree[0]; 234 | isFullHouse = maxThree + maxTwoCard; 235 | if (this.isShort) { 236 | this.pokerStyle[4] = isFullHouse; 237 | } else { 238 | this.pokerStyle[3] = isFullHouse; 239 | } 240 | return; 241 | } 242 | 243 | // flush 244 | if (isFlush.length !== 0) { 245 | isFlush.reverse().length = 5; 246 | if (this.isShort) { 247 | this.pokerStyle[3] = isFlush.join(''); 248 | } else { 249 | this.pokerStyle[4] = isFlush.join(''); 250 | } 251 | return; 252 | } 253 | 254 | // straight 255 | if (this.isStraight() !== '0') { 256 | this.pokerStyle[5] = `${this.isStraight()}`; 257 | return; 258 | } 259 | 260 | // three of kind 261 | if (isThree.length > 0) { 262 | isThreeKind = isThree.join(''); 263 | isThreeKind += highCard[0] + highCard[1]; 264 | this.pokerStyle[6] = isThreeKind; 265 | return; 266 | } 267 | 268 | // tow pair 269 | if (isTwo.length >= 2) { 270 | const towPair = isTwo; 271 | const threeTowPair = towPair[2] || ''; 272 | towPair.length = 2; 273 | isTowPair = towPair.join(''); 274 | // third tow pair card big then high card 275 | const highCardForTowPair = threeTowPair > highCard[0] ? threeTowPair : highCard[0]; 276 | isTowPair += highCardForTowPair; 277 | this.pokerStyle[7] = isTowPair; 278 | return; 279 | } 280 | // pair 281 | if (isTwo.length === 1) { 282 | isPair = isTwo.join(''); 283 | isPair += highCard[0] + highCard[1] + highCard[2]; 284 | this.pokerStyle[8] = isPair; 285 | return; 286 | } 287 | // High card 288 | highCard.length = 5; 289 | this.pokerStyle[9] = highCard.join(''); 290 | } 291 | public getPokerValueCard() { 292 | let valueStyle = ''; 293 | let isFlush = false; 294 | this.pokerStyle.forEach((style, key) => { 295 | if (style !== '0') { 296 | isFlush = key === 1 || this.isShort ? key === 3 : key === 4; 297 | valueStyle = style; 298 | } 299 | }); 300 | const cards = this.cards.filter((card) => { 301 | if (isFlush) { 302 | return valueStyle.indexOf(card[0]) > -1 && card[1] === this.flushColor; 303 | } 304 | return valueStyle.indexOf(card[0]) > -1; 305 | }); 306 | cards.reverse().length = 5; 307 | return cards; 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /client/src/views/home.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | room config 9 | 10 | smallBlind: 11 | 12 | 14 | 15 | 16 | 17 | isShort: 18 | 19 | 21 | 22 | 23 | create 24 | 25 | 26 | create room 28 | 29 | join room 31 | 32 | test record 34 | 35 | 36 | 7 day game history 37 | 38 | 39 | 41 | 42 | 44 | 45 | 46 | 50 | 51 | 52 | 53 | 54 | go 55 | 56 | 57 | 62 | 63 | 64 | 65 | 179 | 291 | -------------------------------------------------------------------------------- /client/src/components/Action.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | fold 7 | check 9 | call 11 | more 13 | allin 15 | 16 | 17 | 19 | {{Math.floor(size)}} 23 | 24 | 25 | 27 | 28 | 30 | 31 | 32 | Allin 34 | 35 | 40 | ok 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 200 | 201 | 202 | 304 | -------------------------------------------------------------------------------- /server/test/app/core/pokerGame.test.ts: -------------------------------------------------------------------------------- 1 | import { PokerGame } from '../../../src/app/core/PokerGame'; 2 | // @ts-ignore 3 | import { expect } from 'chai'; 4 | import { IPlayer } from '../../../src/app/core/Player'; 5 | 6 | describe('test/app/core/pokerGame.test.ts', () => { 7 | const users: IPlayer[] = [ 8 | { 9 | userId: '1', 10 | counter: 511, 11 | nickName: '1', 12 | account: '1', 13 | socketId: '1', 14 | buyIn: 0, 15 | reBuy: 0, 16 | actionSize: 0, 17 | actionCommand: '', 18 | status: 0, 19 | type: '', 20 | }, 21 | { 22 | userId: '2', 23 | counter: 427, 24 | nickName: '2', 25 | account: '2', 26 | socketId: '2', 27 | buyIn: 0, 28 | reBuy: 0, 29 | actionSize: 0, 30 | actionCommand: '', 31 | status: 0, 32 | type: '', 33 | }, 34 | { 35 | userId: '3', 36 | counter: 730, 37 | nickName: '3', 38 | account: '3', 39 | socketId: '3', 40 | buyIn: 0, 41 | reBuy: 0, 42 | actionSize: 0, 43 | actionCommand: '', 44 | status: 0, 45 | type: '', 46 | }, 47 | { 48 | userId: '4', 49 | counter: 744, 50 | nickName: '4', 51 | account: '4', 52 | socketId: '4', 53 | buyIn: 0, 54 | reBuy: 0, 55 | actionSize: 0, 56 | actionCommand: '', 57 | status: 0, 58 | type: '', 59 | }, 60 | // { 61 | // userId: '5', 62 | // counter: 802, 63 | // nickName: '5', 64 | // account: '5', 65 | // socketId: '5', 66 | // buyIn: 0, 67 | // reBuy: 0, 68 | // actionSize: 0, 69 | // actionCommand: '', 70 | // status: 0, 71 | // type: '', 72 | // }, 73 | ]; 74 | 75 | /** 76 | * game ready 77 | */ 78 | // it('game init', async () => { 79 | // const game = new PokerGame({ 80 | // smallBlind: 1, 81 | // users, 82 | // actionRoundComplete: () => {}, 83 | // gameOverCallBack: () => {}, 84 | // autoActionCallBack: () => {}, 85 | // }); 86 | // game.play(); 87 | // expect(game.status).to.equal(EGameStatus.GAME_ACTION); 88 | // expect(game.pot).to.equal(3); 89 | // expect(game.pot).to.equal(3); 90 | // // expect(game.playerLink.getNode(1).node.actionSize).to.equal(1); 91 | // }); 92 | 93 | /** 94 | * game playing 95 | */ 96 | // it('game play', async () => { 97 | // const game = new PokerGame({ 98 | // smallBlind: 1, 99 | // users, 100 | // actionRoundComplete: () => {}, 101 | // gameOverCallBack: () => {}, 102 | // autoActionCallBack: () => {}, 103 | // }); 104 | // game.play(); 105 | // game.action('fold'); 106 | // }); 107 | // 108 | // it('raise check ', async () => { 109 | // const game = new PokerGame({ 110 | // smallBlind: 1, 111 | // users, 112 | // actionRoundComplete: () => { 113 | // if (game.status < 6) { 114 | // game.startActionRound(); 115 | // game.sendCard(); 116 | // } 117 | // }, 118 | // gameOverCallBack: () => {}, 119 | // autoActionCallBack: () => {}, 120 | // }); 121 | // game.play(); 122 | // game.action('raise:9'); 123 | // game.action('call'); 124 | // game.action('check'); 125 | // game.action('check'); 126 | // // game.action('raise:10'); 127 | // // console.log(game.commonCard); 128 | // // console.log(game.pot); 129 | // // console.log(game.getPlayers()); 130 | // // console.log(game.winner); 131 | // // console.log(game.winner[0][0], game.commonCard); 132 | // }); 133 | // 134 | // it('raise call raise fold ', async () => { 135 | // const game = new PokerGame({ 136 | // smallBlind: 1, 137 | // users, 138 | // actionRoundComplete: () => { 139 | // if (game.status < 6) { 140 | // game.startActionRound(); 141 | // game.sendCard(); 142 | // } 143 | // }, 144 | // gameOverCallBack: () => {}, 145 | // autoActionCallBack: () => {}, 146 | // }); 147 | // game.play(); 148 | // game.action('raise:9'); 149 | // game.action('call'); 150 | // // game.action('check'); 151 | // // game.action('check'); 152 | // game.action('raise:90'); 153 | // game.action('fold'); 154 | // // game.action('raise:10'); 155 | // // console.log(game.commonCard); 156 | // // console.log(game.pot); 157 | // // console.log(game.getPlayers()); 158 | // // console.log(game.winner); 159 | // // console.log(game.winner[0][0], game.commonCard); 160 | // }); 161 | // 162 | // it('show down', async () => { 163 | // const game = new PokerGame({ 164 | // smallBlind: 1, 165 | // users, 166 | // actionRoundComplete: () => { 167 | // if (game.status < 6) { 168 | // game.startActionRound(); 169 | // game.sendCard(); 170 | // } 171 | // }, 172 | // gameOverCallBack: () => {}, 173 | // autoActionCallBack: () => {}, 174 | // }); 175 | // game.play(); 176 | // // pre flop 177 | // game.action('raise:9'); 178 | // game.action('call'); 179 | // // flop 180 | // game.action('raise:90'); 181 | // game.action('call'); 182 | // // turn 183 | // game.action('check'); 184 | // game.action('check'); 185 | // // river 186 | // game.action('raise:90'); 187 | // game.action('call'); 188 | // // show down 189 | // // game.action('raise:10'); 190 | // // console.log(game.commonCard); 191 | // // console.log(game.pot); 192 | // // console.log(game.getPlayers()); 193 | // // console.log(game.winner); 194 | // // console.log(game.winner[0][0], game.commonCard); 195 | // }); 196 | // 197 | // it('all player allin for pre flop', async () => { 198 | // const game = new PokerGame({ 199 | // smallBlind: 1, 200 | // users, 201 | // actionRoundComplete: () => { 202 | // if (game.status < 6) { 203 | // game.startActionRound(); 204 | // game.sendCard(); 205 | // } 206 | // }, 207 | // gameOverCallBack: () => {}, 208 | // autoActionCallBack: () => {}, 209 | // }); 210 | // game.play(); 211 | // // pre flop 212 | // game.action('raise:9'); 213 | // game.action('allin'); 214 | // game.action('allin'); 215 | // // game over 216 | // // game.action('raise:10'); 217 | // // console.log('all player ------commonCard', game.commonCard); 218 | // // console.log('all player ------pot', game.pot); 219 | // // console.log('all player ------getPlayers', game.getPlayers()); 220 | // // console.log('all player ------winner', game.winner); 221 | // // console.log(game.winner[0][0], game.commonCard); 222 | // }); 223 | // 224 | it('one player allin', async () => { 225 | const game = new PokerGame({ 226 | smallBlind: 1, 227 | isShort: false, 228 | users, 229 | actionRoundComplete: () => { 230 | if (game.status < 6) { 231 | game.startActionRound(); 232 | game.sendCard(); 233 | } 234 | }, 235 | gameOverCallBack: () => {}, 236 | autoActionCallBack: () => {}, 237 | }); 238 | game.play(); 239 | // pre flop 240 | console.log('curr----------------------------1', game.currPlayer); 241 | // game.action('fold'); // utg 242 | // console.log('curr----------------------------2', game.currPlayer); 243 | game.action('raise:6'); // co 244 | game.action('call'); // D 245 | game.action('call'); // sb 246 | game.action('call'); // sb 247 | game.action('check'); // bb 248 | game.action('raise:12'); // d 249 | game.action('call'); // bb 250 | game.action('call'); // bb 251 | 252 | // game.action('call'); // bb 253 | game.action('fold'); // sb 254 | game.action('check'); // bb 255 | game.action('check'); // bb 256 | game.action('raise:30'); // bb 257 | game.action('raise:90'); // bb 258 | game.action('fold'); // sb 259 | game.action('raise:180'); // bb 260 | game.action('raise:360'); // bb 261 | game.action('allin'); // bb 262 | game.action('allin'); // bb 263 | // game.action('fold'); // bb 264 | // console.log(game.pot, 'pot1'); 265 | 266 | // game.action('check'); // bb 267 | // game.action('fold'); // sb 268 | // game.action('fold'); // bb 269 | // game.action('check'); // bb 270 | // game.action('raise:30'); // bb 271 | // console.log(game.pot, 'pot'); 272 | // game.action('call'); // bb 273 | // game.action('check'); // bb 274 | // game.action('allin'); // bb 275 | // game.action('allin'); // bb 276 | // game.action('fold'); // bb 277 | console.log(game.winner); 278 | console.log(game.pot, 'pot2'); 279 | // // console.log('curr----------------------------3', game.currPlayer); 280 | // game.action('check'); // BB 200 281 | // game.action('check'); // utg 282 | // game.action('raise:20'); // d 283 | // game.action('call'); // sb 284 | // game.action('fold'); // bb 285 | // game.action('fold'); // utg 286 | // game.action('allin'); // sb 287 | 288 | // console.log('curr----------------------------4', game.currPlayer); 289 | // game.action('raise:20'); // bb 1000 290 | // console.log('curr----------------------------1', game.currPlayer); 291 | // game.action('call'); // utg 292 | // game.action('fold'); // sb 293 | // game.action('call'); // bb 294 | // game over 295 | // game.action('raise:10'); 296 | console.log(game.commonCard); 297 | console.log(game.slidePots, 'slidePots'); 298 | console.log(game.getPlayers()); 299 | console.log(game.winner); 300 | }); 301 | // 302 | // it('player fold pre flop', async () => { 303 | // const game = new PokerGame({ 304 | // smallBlind: 1, 305 | // users, 306 | // actionRoundComplete: () => { 307 | // if (game.status < 6) { 308 | // game.startActionRound(); 309 | // game.sendCard(); 310 | // } 311 | // }, 312 | // gameOverCallBack: () => {}, 313 | // autoActionCallBack: () => {}, 314 | // }); 315 | // game.play(); 316 | // // pre flop 317 | // game.action('fold'); 318 | // // game over 319 | // // game.action('raise:10'); 320 | // // console.log(game.commonCard); 321 | // // console.log(game.pot); 322 | // // console.log(game.getPlayers()); 323 | // // console.log(game.winner); 324 | // // console.log(game.winner[0][0], game.commonCard); 325 | // }); 326 | // // flop 327 | // // turn 328 | // // river 329 | // // chip in 330 | // // has Allin need separate pot 331 | // // many allin 332 | // 333 | // /** 334 | // * game over 335 | // */ 336 | // it('game over', async () => { 337 | // const game = new PokerGame({ 338 | // smallBlind: 1, 339 | // users, 340 | // actionRoundComplete: () => { 341 | // if (game.status < 6) { 342 | // game.startActionRound(); 343 | // game.sendCard(); 344 | // } 345 | // }, 346 | // gameOverCallBack: () => {}, 347 | // autoActionCallBack: () => {}, 348 | // }); 349 | // game.play(); 350 | // game.action('raise:10'); 351 | // game.action('allin'); 352 | // game.action('fold'); 353 | // 354 | // // console.log('game over----------- common', game.commonCard); 355 | // // console.log(game.pot); 356 | // // console.log(game.getPlayers()); 357 | // // console.log(game.winner); 358 | // // only one player 359 | // // last player, other player fold 360 | // 361 | // // multiple player 362 | // // last player, has all in player 363 | // // all player all in 364 | // // one player all in 365 | // // many player all in 366 | // 367 | // // winner 368 | // // one winner 369 | // // multiple winner 370 | // // bisecting pot 371 | // // allin player winner and small pot, multiple second winner bisecting pot 372 | // // allin player winner and small pot, one second winner 373 | // // all player allin, winner can't win all pot, 374 | // }); 375 | // /** 376 | // * count 377 | // */ 378 | // it('count', async () => { 379 | // 380 | // }); 381 | // // has other pot 382 | }); 383 | --------------------------------------------------------------------------------