diff --git a/package-lock.json b/package-lock.json index 0fe2b9a..94dcfb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -446,6 +446,14 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "axios": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", + "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", diff --git a/package.json b/package.json index 732708d..f511c37 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dev": "DEBUG=colyseus:*,jc:* node --require ts-node/register --inspect src/index.ts", "dev:less": "DEBUG=colyseus:command,jc:* node --require ts-node/register --inspect=0.0.0.0:9229 src/index.ts", "dev:jc": "DEBUG=jc:* node --require ts-node/register --inspect=0.0.0.0:9229 src/index.ts", + "dev:robot": "DEBUG=jc:* node --require ts-node/register --inspect=0.0.0.0:9228 src/robot.ts", "loadtest": "colyseus-loadtest loadtest/example.ts --room my_room --numClients 3", "test": "echo \"Error: no test specified\" && exit 1" }, @@ -28,6 +29,7 @@ "@colyseus/monitor": "^0.12.2", "@colyseus/proxy": "^0.12.2", "@colyseus/social": "^0.10.9", + "axios": "^0.21.0", "colyseus": "^0.14.0", "cors": "^2.8.5", "debug": "^4.3.1", diff --git a/pm2_dev.sh b/pm2_dev.sh index fdbbcda..27441e7 100755 --- a/pm2_dev.sh +++ b/pm2_dev.sh @@ -1 +1,2 @@ pm2 start npm --name "card" -- run "dev:jc" +pm2 start npm --name "robot" -- run "dev:robot" diff --git a/src/robot.ts b/src/robot.ts new file mode 100644 index 0000000..1a5911f --- /dev/null +++ b/src/robot.ts @@ -0,0 +1,22 @@ + +import express from "express"; +import cors from "cors"; +import bodyParser from 'body-parser'; +import mainCtrl from './robot/main.controller'; +import {initData} from "./common/GConfig"; + +const app = express() +const port = Number(process.env.PORT || 2500); + +initData(); + +app.use(cors()); +app.use(express.json()); +app.use(bodyParser.json({})); + + +app.use('/robot', mainCtrl); + +app.listen(port, function () { + console.log(`App is listening on port ${port}!`); +}); diff --git a/src/robot/Robot.ts b/src/robot/Robot.ts new file mode 100644 index 0000000..893dd8e --- /dev/null +++ b/src/robot/Robot.ts @@ -0,0 +1,411 @@ +import {Client, Room} from "colyseus.js"; +import {error, robotLog as log, robotLog as debug} from "../common/Debug"; +import {HeroCfg} from "../cfg/parsers/HeroCfg"; +import {BaseConst} from "../constants/BaseConst"; +import arrUtil from "../utils/array.util"; +import {GameStateConst} from "../constants/GameStateConst"; +import {Card} from "../rooms/schema/Card"; +import {Player} from "../rooms/schema/Player"; +import {EffectCardCfg} from "../cfg/parsers/EffectCardCfg"; +import {SkillTargetType} from "../rooms/logic/skill/SkillConst"; +import CfgMan from "../rooms/logic/CfgMan"; + +export class Robot { + host: string; + roomId: string; + room: Room; + sessionId: string; + client: Client; + myTurn: boolean = false; + player: Player; + + constructor(host: string, roomId: string) { + this.host = host; + this.roomId = roomId; + this.client = new Client(host); + } + + async connect() { + try { + this.room = await this.client.joinById(this.roomId); + this.addListeners(); + this.sessionId = this.room.sessionId; + this.setReady(); + return this.room.sessionId; + } catch (err) { + error(`error join room ${this.host}, ${this.roomId}`) + } + } + + addListeners() { + let self = this; + this.room.onMessage("*", (type, data) => { + debug("[ROBOT] received message:", type, "=>", data); + switch (type) { + case 'draw_card_s2c': + // if (data.player == self.sessionId) { + // self.cards = self.cards.concat(data.cards); + // } + break; + case 'player_ready_s2c': + break; + case 'eat_card_s2c': + if (data.errcode == 0 && data.player == self.sessionId) { + self.selectPet(); + } + break; + } + }); + + this.room.onLeave(function () { + debug("[ROBOT] LEFT ROOM", arguments); + self.room.removeAllListeners(); + self.room.leave(); + }); + /** + * 监听房间状态的变更, state含有当前房间游戏的所有公共信息, 任何值的更改都会触发该方法 + * */ + this.room.onStateChange(function (state) { + self.player = state.players.get(self.sessionId); + // self.players = state.players; + }); + // 也可以监听state下某个特定值的变更, 比如下面是监听 当前轮的clientid + this.room.state.listen("currentTurn", (currentValue: string) => { + self.myTurn = currentValue === this.sessionId; + if (self.myTurn) { + self.discard(); + } + }); + this.room.state.listen("subTurn", (currentValue: string, previousValue: string) => { + // self.mySubTurn = currentValue === self.sessionId; + // if (self.mySubTurn) { + // setTimeout(self.giveup.bind(self), self.delay); + // } + }); + // 监听游戏状态的改变 + this.room.state.listen("gameState", (currentValue: number, previousValue: number) => { + switch (currentValue) { + case GameStateConst.DETERMINE_TURN: + // console.log('比大小阶段'); + //TODO: 随机分牌比大小阶段, 需要 + break; + case GameStateConst.CHANGE_HERO: + // console.log('英雄选择阶段'); + self.selectHero(); + break; + case GameStateConst.STATE_CHANGE_CARD: + // console.log('开局换卡阶段'); + self.changeCard(); + break; + case GameStateConst.STATE_BEGIN_EAT: + if (!self.myTurn) { + self.eatOrGiveUp(); + } + break; + case GameStateConst.STATE_ROUND_RESULT: + console.log('结算轮'); + break; + } + }); + } + private delay(max: number, min?: number) { + min = min || 0; + let milliseconds = (Math.random()*(max-min)+min) * 1000 | 0; + return new Promise(resolve => setTimeout(resolve, milliseconds)); + } + private reply(messageType: string, message: any) { + this.room.send(messageType, message); + } + + private checkTriple(cardArr: Card[], card?: Card): Card[] { + if (card) cardArr.push(card); + let pointMap: Map = new Map(); + let cardIdSet: Set = new Set(); + for (let c of cardArr) { + if (c.type !== 1) { + continue; + } + if (pointMap.has(c.number)) { + let arr = pointMap.get(c.number); + arr.push(c); + pointMap.set(c.number, arr); + } else { + pointMap.set(c.number, [c]); + } + cardIdSet.add(c.number); + } + let fetched = false; + let result:Card[] = []; + // 优先出对子 + for (let [point, arr] of pointMap) { + if (card) { + if (point == card.number && arr.length >= 3) { + fetched = true; + result = arr; + break; + } + } else { + if (arr.length >= 3) { + fetched = true; + result = arr; + break; + } + } + + } + if (fetched) { + return result; + } + + let cardIds = [...cardIdSet]; + cardIds.sort((a, b) => a - b); + let tmp = []; + for (let i = 0, length = cardIds.length; i < length; i++) { + let cur = cardIds[i]; + i == 0 && tmp.push(cur); + if (i > 0) { + if (cur != tmp[i - 1] + 1) { + tmp.length = 0; + } + tmp.push(cur); + } + if (card) { + if (tmp.indexOf(card.number) >= 0 && tmp.length >=3 ){ + break; + } + } else { + if (tmp.length >= 3) { + break; + } + } + } + + if (tmp.length >= 3) { + let subTmp = []; + for (let i = tmp[0] - 1; i > 0 ; i -- ) { + if (cardIdSet.has(i)) { + subTmp.push(i); + } else { + break; + } + } + for (let i = tmp[tmp.length]; i < cardIdSet.size; i ++) { + if (cardIdSet.has(i)) { + subTmp.push(i); + } else { + break; + } + } + tmp = tmp.concat(subTmp); + for (let point of tmp) { + if (card && point === card.number) { + result.push(card); + } else { + result.push(pointMap.get(point)[0]); + } + } + return result; + } else { + return arrUtil.randomGet(cardArr, 1); + } + } + /** + * 随机获取敌对玩家 + * @private + */ + private getEnemyPlayer(): Player { + let enemys = []; + for (let [,player] of this.room.state.players) { + if (player.team !== this.player.team) { + enemys.push(player); + } + } + return arrUtil.randomOne(enemys); + } + + /** + * 随机获取可用的随从 + * @param player + * @private + */ + private getRandomPet(player: Player): number { + let pets = []; + for (let [, pet] of player.pets) { + if (pet.ap > 0 && pet.state == 1) + pets.push(pet); + } + let result; + if (pets.length > 0) { + result = arrUtil.randomOne(pets); + } + return result ? result.pos : -1; + } + + // >>>>>>>>>>>>>>>>>> begin + /** + * 开局选择英雄 + * @private + */ + private async selectHero() { + let heroMap: Map = global.$cfg.get(BaseConst.HERO); + let heroArr = [...heroMap.values()]; + let hero = arrUtil.randomGet(heroArr, 1); + await this.delay(2); + this.reply('select_hero_c2s', { + heroId: hero[0].id + }); + } + + /** + * 开局装备 + * @private + */ + private async setReady() { + this.reply('play_ready_c2s', ''); + } + + /** + * 开局换牌 + * @private + */ + private async changeCard() { + let cardIds: number[] = []; + await this.delay(2); + this.reply('change_card_c2s', { + cards: cardIds + }); + } + + /** + * 出牌 + * @private + */ + private async discard() { + await this.delay(3); + let self = this; + let cardArr = [...self.player.cards.values()]; + let cards = self.checkTriple(cardArr); + if (!cards) { + return; + } + let cardIds = cards.map(o => o.id); + self.reply('discard_card_c2s', { + cards: cardIds + }); + } + + /** + * 吃牌或者放弃 + * @private + */ + private async eatOrGiveUp() { + await this.delay(2); + let targetCard = [...this.room.state.cards.values()][0]; + let cardArr = [...this.player.cards.values()]; + let tmpCards = this.checkTriple(cardArr, targetCard); + let next = this.giveup.bind(this); + if (tmpCards.length > 1) { + let cardIds: number[] = []; + for (let card of tmpCards) { + if (card.id !== targetCard.id) { + cardIds.push(card.id); + } + } + next = this.eatCard.bind(this, cardIds, targetCard.id); + } + + next.apply(this); + } + + /** + * 吃牌 + * @param cardIds + * @param target + * @private + */ + private eatCard(cardIds: number[], target: number) { + log(`${this.sessionId} 吃牌 ${cardIds} -> ${target}`); + this.reply('eat_card_c2s', { + cards: cardIds, + target + }); + } + + /** + * 放弃吃牌 + * @private + */ + private giveup () { + this.reply('give_up_eat_c2s', {}); + } + + + /** + * 选择一个法术或者一个随从 + * @private + */ + private async selectPet() { + await this.delay(5, 0.2); + let cards = [...this.room.state.cards.values()]; + let result; + let effectMap: Map = global.$cfg.get(BaseConst.EFFECTCARD); + for (let card of cards) { + let effect = effectMap.get(card.effect); + if (effect.type_id == 1) { + result = card; + break; + } + } + if (!result) { + result = arrUtil.randomGet(cards, 1)[0]; + } + let targetType: SkillTargetType = CfgMan.getTargetByCard(result.id); + let targetPlayer; + let targetPos; + switch (targetType) { + case SkillTargetType.ENEMY_PLAYER: + targetPlayer = this.getEnemyPlayer(); + break; + case SkillTargetType.ENEMY_PET: + for (let [,player] of this.room.state.players) { + if (player.team !== this.player.team) { + let pos = this.getRandomPet(player); + if (pos > - 1) { + targetPlayer = player; + targetPos = pos; + break; + } + } + } + break; + case SkillTargetType.FRIEND_PET: + for (let [,player] of this.room.state.players) { + if (player.team == this.player.team) { + let pos = this.getRandomPet(player); + if (pos > - 1) { + targetPlayer = player; + targetPos = pos; + break; + } + } + } + break; + case SkillTargetType.SELF_PET: + let pos = this.getRandomPet(this.player); + if (pos > - 1) { + targetPlayer = this.player; + targetPos = pos; + break; + } + break; + } + this.reply('select_pet_c2s', { + card: result.id, + player: targetPlayer?.id, + pos: targetPos, + effCards: [] + }) + } + + +} diff --git a/src/robot/RobotManage.ts b/src/robot/RobotManage.ts new file mode 100644 index 0000000..0198408 --- /dev/null +++ b/src/robot/RobotManage.ts @@ -0,0 +1,9 @@ +import {Robot} from "./Robot"; + +export class RobotManage { + + async addOne({host, room}: { host: string, room: string }) { + let robot = new Robot(host, room); + let sessionId = await robot.connect(); + } +} diff --git a/src/robot/main.controller.ts b/src/robot/main.controller.ts new file mode 100644 index 0000000..bca5189 --- /dev/null +++ b/src/robot/main.controller.ts @@ -0,0 +1,25 @@ +import express from 'express'; +import {robotLog as debug} from "../common/Debug"; +import {singleton} from "../common/Singleton"; +import {RobotManage} from "./RobotManage"; + +const router = express.Router(); + +router.get('/create', async (req, res, next) => { + let query = req.query; + let {host, room } = query; + host = host as string; + room = room as string; + debug(`receive create robot msg: ${host}, ${room}`); + let manage = singleton(RobotManage); + try { + await manage.addOne({host, room}); + res.json({errcode: 0}); + } catch (err) { + res.json({errcode: 1}) + } + + +}) + +export default router; diff --git a/src/rooms/GeneralRoom.ts b/src/rooms/GeneralRoom.ts index 2df028b..ca7dad4 100644 --- a/src/rooms/GeneralRoom.ts +++ b/src/rooms/GeneralRoom.ts @@ -19,7 +19,7 @@ import {GMCommand} from "./commands/GMCommand"; import {GameStateConst} from "../constants/GameStateConst"; import {GameRestartCommand} from "./commands/GameRestartCommand"; import {RobotClient} from "../robot/RobotClient"; - +import axios from 'axios'; export class GeneralRoom extends Room { dispatcher = new Dispatcher(this); @@ -100,7 +100,7 @@ export class GeneralRoom extends Room { } //TODO: 掉线逻辑 async onLeave (client: Client, consented: boolean) { - if (this.state.gameState === GameStateConst.STATE_GAME_OVER) { + if (this.state.gameState === GameStateConst.STATE_GAME_OVER || this.state.gameState === GameStateConst.STATE_WAIT_JOIN) { this.state.players.delete(client.sessionId); this.bUserLeft(client.sessionId); } else { @@ -210,28 +210,41 @@ export class GeneralRoom extends Room { } addRobot(playerId?: string) { - const sessionId = playerId || generateId(); - let client = new RobotClient(sessionId, this.state, this.clock, this['onMessageHandlers']); - if (this.reservedSeatTimeouts[sessionId]) { - clearTimeout(this.reservedSeatTimeouts[sessionId]); - delete this.reservedSeatTimeouts[sessionId]; + let data = { + host: 'ws://127.0.0.1:2567', + room: this.roomId, } - // get seat reservation options and clear it - const options = this.reservedSeats[sessionId]; - delete this.reservedSeats[sessionId]; - this.clients.push(client); - client.ref.once('close', this['_onLeave'].bind(this, client)); - // client.ref.on('message', this.onMessage.bind(this, client)); - const reconnection = this.reconnections[sessionId]; - if (reconnection) { - reconnection.resolve(client); - } - else { - if (this.onJoin) { - this.onJoin(client, options); - } - delete this.reservedSeats[sessionId]; - } - this._events.emit('join', client); + axios.get('http://127.0.0.1:2500/robot/create', { + params: data + }).then((res) => { + debugRoom(res.status); + debugRoom(res.data); + }).catch((err) => { + error(err); + }) + + // const sessionId = playerId || generateId(); + // let client = new RobotClient(sessionId, this.state, this.clock, this['onMessageHandlers']); + // if (this.reservedSeatTimeouts[sessionId]) { + // clearTimeout(this.reservedSeatTimeouts[sessionId]); + // delete this.reservedSeatTimeouts[sessionId]; + // } + // // get seat reservation options and clear it + // const options = this.reservedSeats[sessionId]; + // delete this.reservedSeats[sessionId]; + // this.clients.push(client); + // client.ref.once('close', this['_onLeave'].bind(this, client)); + // // client.ref.on('message', this.onMessage.bind(this, client)); + // const reconnection = this.reconnections[sessionId]; + // if (reconnection) { + // reconnection.resolve(client); + // } + // else { + // if (this.onJoin) { + // this.onJoin(client, options); + // } + // delete this.reservedSeats[sessionId]; + // } + // this._events.emit('join', client); } } diff --git a/src/rooms/commands/DiscardCommand.ts b/src/rooms/commands/DiscardCommand.ts index 3888823..44c00da 100644 --- a/src/rooms/commands/DiscardCommand.ts +++ b/src/rooms/commands/DiscardCommand.ts @@ -54,6 +54,7 @@ export class DiscardCommand extends Command = global.$cfg.get(BaseConst.SYSTEMCARD); let cfgs = []; for (let [, cfg] of numCfgMap) { - if (cfg.type_id == 1) { + if (cfg.weight.indexOf(effectId + '') >= 0) { cfgs.push(cfg); } }