From 498a81a3b7bbf6caf926a08f19ab24f81305a031 Mon Sep 17 00:00:00 2001 From: zhl Date: Fri, 19 Mar 2021 15:23:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=9C=BA=E5=99=A8=E4=BA=BA?= =?UTF-8?q?=E5=87=BA=E7=89=8C=E9=80=BB=E8=BE=91,=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=94=BE=E5=BC=83=E5=90=83=E7=89=8C=E5=92=8C=E8=83=A1=E7=89=8C?= =?UTF-8?q?=E7=9A=84=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cfg/GameEnv.ts | 69 ++++- src/cfg/RoomOptions.ts | 2 +- src/robot/Robot.ts | 21 +- src/robot/RobotClient.ts | 20 +- src/rooms/commands/BeginGameCommand.ts | 10 +- src/rooms/commands/SelectPetCommand.ts | 3 +- src/utils/assistant.util.ts | 365 +++++++++++++++---------- 7 files changed, 313 insertions(+), 177 deletions(-) diff --git a/src/cfg/GameEnv.ts b/src/cfg/GameEnv.ts index 5a3996d..e5a6fed 100644 --- a/src/cfg/GameEnv.ts +++ b/src/cfg/GameEnv.ts @@ -66,18 +66,24 @@ export class GameEnv { public petInheritRate: number // 低级机器人的胜率值 public robotLvlLow: number - public robotRateLow: number // 中等机器人的胜率值 public robotLvlMid: number - public robotRateMid: number // 高级机器人的胜率值 public robotLvlHigh: number - public robotRateHigh: number + // 变态机器人的胜率值 public robotLvlExtra: number - public robotRateExtra: number + + + // 电脑作弊机率 + public robotCheatRate: number[] + // 电脑放弃吃牌机率 + public robotNoEatRate: number[] + // 电脑放弃胡牌机率 + public robotNoTripleRate: number[] + // 等待玩家确认的最长时间 public maxWaitingTime: number @@ -121,21 +127,54 @@ export class GameEnv { this.robotLvlMid = parseInt(data.get(BaseConst.ROBOT_LVL_MID).value) this.robotLvlHigh = parseInt(data.get(BaseConst.ROBOT_LVL_HIGHT).value) this.robotLvlExtra = parseInt(data.get(BaseConst.ROBOT_LVL_EXTRA).value) - this.robotRateLow = parseInt(data.get(BaseConst.ROBOT_RATE_LOW).value) - this.robotRateMid = parseInt(data.get(BaseConst.ROBOT_RATE_MID).value) - this.robotRateHigh = parseInt(data.get(BaseConst.ROBOT_RATE_HIGHT).value) - this.robotRateExtra = parseInt(data.get(BaseConst.ROBOT_RATE_EXTRA).value) + this.robotCheatRate = [ + +data.get(BaseConst.ROBOT_RATE_LOW).value, + +data.get(BaseConst.ROBOT_RATE_MID).value, + +data.get(BaseConst.ROBOT_RATE_HIGHT).value, + +data.get(BaseConst.ROBOT_RATE_EXTRA).value + ] + this.robotNoEatRate = [ + +(data.get(99053)?.value || 0) , + +(data.get(99054)?.value || 0), + +(data.get(99055)?.value || 0), + +(data.get(99056)?.value || 0), + ] + this.robotNoTripleRate = [ + +(data.get(99057)?.value || 0), + +(data.get(99058)?.value || 0), + +(data.get(99059)?.value || 0), + +(data.get(99060)?.value || 0), + ] this.maxWaitingTime = 60 } - public getCheatRate(val: number) { - if (val < this.robotRateMid) { - return this.robotRateLow + + /** + * 获取机器人配置 + * @param {number} lvl + * @return {{noEatRate: number, lvl: number, cheatRate: number, noTripleRate: number}} + */ + public getRobotCfg(lvl: number) { + const cheatRate = this.robotCheatRate[lvl] || 0 + const noEatRate = this.robotNoEatRate[lvl] || 0 + const noTripleRate = this.robotNoTripleRate[lvl] || 0 + return {lvl, cheatRate, noEatRate, noTripleRate} + } + + /** + * 确定机器人等级 + * @param {number} val 玩家十场胜率 + * @return {number} + */ + public getRobotLvl(val: number) { + if (val < this.robotLvlMid) { + return 0 } if (val < this.robotLvlHigh) { - return this.robotRateMid + return 1 } else if (val < this.robotLvlExtra) { - return this.robotRateHigh - } else if (val >= this.robotRateExtra){ - return this.robotRateExtra + return 2 + } else if (val >= this.robotLvlExtra){ + return 3 } } + } diff --git a/src/cfg/RoomOptions.ts b/src/cfg/RoomOptions.ts index 315a113..44c70fb 100644 --- a/src/cfg/RoomOptions.ts +++ b/src/cfg/RoomOptions.ts @@ -60,7 +60,7 @@ export class RoomOptions { * @param opt */ public multipleEat(opt?: any) { - return 0 + return 1 } /** diff --git a/src/robot/Robot.ts b/src/robot/Robot.ts index c6b3d43..0f4cab3 100644 --- a/src/robot/Robot.ts +++ b/src/robot/Robot.ts @@ -5,6 +5,7 @@ import { Player } from '../rooms/schema/Player' import assistantUtil from '../utils/assistant.util' import { delay, wait } from '../decorators/cfg' import { RULE_CANEAT } from '../cfg/RoomOptions' +import { GameEnv } from '../cfg/GameEnv' export class Robot { host: string @@ -15,6 +16,9 @@ export class Robot { myTurn: boolean = false player: Player cheatRate: number = 0 + noEatRate: number = 0 + noTripleRate: number = 0 + lvl: number = 0 constructor(host: string, roomId: string) { this.host = host @@ -65,9 +69,13 @@ export class Robot { self.selectPet() } break - case 'update_change_rate': - log(`update cheat rate to: ${data.val}`) - self.cheatRate = data.val + case 'update_robot_level': + const { cheatRate, noEatRate, noTripleRate } = new GameEnv().getRobotCfg(data.lvl) + log(`update robot lvl to: ${data.lvl}, cheatRate: ${cheatRate}, noEatRate: ${noEatRate}, noTripleRate: ${noTripleRate}`) + this.lvl = data.lvl + this.cheatRate = cheatRate + this.noEatRate = noEatRate + this.noTripleRate = noTripleRate break } }) @@ -175,11 +183,13 @@ export class Robot { } let self = this let cardArr = [...self.player.cards.values()] - let result = assistantUtil.checkDiscard({cardArr, card: targetCard, rate: this.cheatRate}) + let result = assistantUtil.discard({cardArr, eatCard: targetCard, rate: this.cheatRate, noEatRate: this.noEatRate, noTripleRate: this.noTripleRate}) let cards = result.cards; if (!cards || cards.length == 0) { return } + // for test + // cards = cardArr.randomGet(1) let cardIds: number[] = [] let hasEatCard = false for (let card of cards) { @@ -205,13 +215,14 @@ export class Robot { /** * 吃牌或者放弃 + * 这个方法逻辑已经有问题了, 暂时没用, 如果需要时, 须更改 * @private */ @wait('maxEatTime') private async eatOrGiveUp() { let targetCard = [...this.room.state.cards.values()][0] let cardArr = [...this.player.cards.values()] - let result = assistantUtil.checkDiscard({cardArr, card: targetCard, rate: this.cheatRate}) + let result = assistantUtil.discard({cardArr, eatCard: targetCard, rate: this.cheatRate, noEatRate: this.noEatRate, noTripleRate: this.noTripleRate}) let tmpCards = result.cards; let next = this.giveup.bind(this) if (tmpCards.length > 1 && targetCard.type === 1) { diff --git a/src/robot/RobotClient.ts b/src/robot/RobotClient.ts index d67e857..55db5a4 100644 --- a/src/robot/RobotClient.ts +++ b/src/robot/RobotClient.ts @@ -9,6 +9,7 @@ import { Player } from '../rooms/schema/Player' import assistantUtil from '../utils/assistant.util' import { wait } from '../decorators/cfg' import { RULE_CANEAT } from '../cfg/RoomOptions' +import { GameEnv } from '../cfg/GameEnv' /** * 服务端辅助机器人 @@ -32,6 +33,9 @@ export class RobotClient implements Client { listenerTurn: any active: boolean = false cheatRate: number = 0 + noEatRate: number = 0 + noTripleRate: number = 0 + lvl: number = 0 constructor(sessionId: string, state: CardGameState, onMessageHandlers: { [id: string]: (client: Client, message: any) => void }) { this.sessionId = sessionId @@ -87,10 +91,15 @@ export class RobotClient implements Client { self.selectPet() } break - case 'update_change_rate': - log(`update cheat rate to: ${data.val}`) - self.cheatRate = data.val + case 'update_robot_level': + const { cheatRate, noEatRate, noTripleRate } = new GameEnv().getRobotCfg(data.lvl) + log(`update robot lvl to: ${data.lvl}, cheatRate: ${cheatRate}, noEatRate: ${noEatRate}, noTripleRate: ${noTripleRate}`) + this.lvl = data.lvl + this.cheatRate = cheatRate + this.noEatRate = noEatRate + this.noTripleRate = noTripleRate break + } } @@ -152,7 +161,7 @@ export class RobotClient implements Client { } let self = this let cardArr = [...self.selfPlayer.cards.values()] - let result = assistantUtil.checkDiscard({cardArr, card: targetCard, rate: this.cheatRate}) + let result = assistantUtil.discard({cardArr, eatCard: targetCard, rate: this.cheatRate, noEatRate: this.noEatRate, noTripleRate: this.noTripleRate}) let cards = result.cards if (!cards || cards.length == 0) { return @@ -182,13 +191,14 @@ export class RobotClient implements Client { /** * 吃牌或者放弃 + * 这个方法逻辑已经有问题了, 暂时没用, 如果需要时, 须更改 * @private */ @wait('maxEatTime') private async eatOrGiveUp() { let targetCard = [...this.svrstate.cards.values()][0] let cardArr = [...this.selfPlayer.cards.values()] - let result = assistantUtil.checkDiscard({cardArr, card: targetCard, rate: this.cheatRate}) + let result = assistantUtil.discard({cardArr, eatCard: targetCard, rate: this.cheatRate, noEatRate: this.noEatRate, noTripleRate: this.noTripleRate}) let tmpCards = result.cards let next = this.giveup.bind(this) if (tmpCards.length > 1 && targetCard.type === 1) { diff --git a/src/rooms/commands/BeginGameCommand.ts b/src/rooms/commands/BeginGameCommand.ts index 0b723ad..733d450 100644 --- a/src/rooms/commands/BeginGameCommand.ts +++ b/src/rooms/commands/BeginGameCommand.ts @@ -44,12 +44,10 @@ export class BeginGameCommand extends Command { } let assistClient = this.room.getAssistClient(player.id) - let rate = new GameEnv().getCheatRate(oplayer.winRate * 100) - if (rate) { - debugRoom(`opposite play win rate: ${ oplayer.winRate }, robot cheat rate: ${ rate }`) - client.send('update_change_rate', { val: rate }) - assistClient.send('update_change_rate', { val: rate }) - } + let lvl = new GameEnv().getRobotLvl(oplayer.winRate * 100) + debugRoom(`opposite play win rate: ${ oplayer.winRate }, robot lvl: ${ lvl }`) + client.send('update_robot_level', { lvl }) + assistClient.send('update_robot_level', { lvl }) } } diff --git a/src/rooms/commands/SelectPetCommand.ts b/src/rooms/commands/SelectPetCommand.ts index 71aa424..fe4438f 100644 --- a/src/rooms/commands/SelectPetCommand.ts +++ b/src/rooms/commands/SelectPetCommand.ts @@ -113,7 +113,8 @@ export class SelectPetCommand extends Command 1) { + const { selfEat } = assistantUtil.checkDiscard({cardArr: [...player.cards.values()]}) + if (multipEat && selfEat) { this.state.updateGameState(GameStateConst.STATE_BEGIN_DRAW) this.state.updateGameTurn(player.id, this.state.eatCount + 1) debugRoom(`more eatcount for player ${player.id}, ${this.state.eatCount}`) diff --git a/src/utils/assistant.util.ts b/src/utils/assistant.util.ts index 01f515c..213879c 100644 --- a/src/utils/assistant.util.ts +++ b/src/utils/assistant.util.ts @@ -24,169 +24,246 @@ function pushMapVal(map: Map, key: number, value: Card) { } } +/** + * 获取一组含有特殊点数的豹子牌 + * @param {Map} pointMap + * @param {number} special + * @return {Card[]} + */ +function seekPairWithSpecial(pointMap: Map, special?: Card) { + let minCount = new GameEnv().otherEatCount + let result: Card[] = [] + for (let [point, arr] of pointMap) { + if (point == special?.number && arr.length >= minCount) { + result = arr + break + } + } + return result +} + +/** + * 获取一组不!!含有特殊点数的豹子牌 + * @param {Map} pointMap + * @param {number} special + * @return {Card[]} + */ +function seekPairNoSpecial(pointMap: Map, special?: Card) { + let minCount = new GameEnv().selfEatCount + let result: Card[] = [] + for (let [point, arr] of pointMap) { + if (arr.length >= minCount ) { + if (!!special && point !== special?.number) { + result = arr + break + } else if (!special) { + result = arr + break + } + } + } + return result +} +/** + * 检查顺子 + * @param pointMap + * @param {number[]} points 升序的distinct点数数组 + * @param {number} special 特殊点数 + * @param {boolean} hasSpecial true: 必须包含特殊点数 + * false: 不包含特殊点数, + * 如果特殊点数不存在, 那么忽略该值 + */ +function seekSeq(pointMap: Map, points: number[], hasSpecial: boolean, special?: Card) { + let minLength = special? new GameEnv().otherEatCount : new GameEnv().selfEatCount + let tmp: number[] = [] + for (let i = 0, length = points.length; i < length; i++) { + let cur = points[i] + if (i > 0) { + if ((cur != tmp[tmp.length - 1] + 1)) { + if (tmp.length < minLength) { + tmp.length = 0 + } else { + if (hasSpecial && !!special && tmp.indexOf(special.number) == -1) { + tmp.length = 0 + } else { + break + } + } + } + } + if ((!hasSpecial && !!special && cur == special.number) ) { + continue + } + tmp.push(cur) + } + // 将获取到的点数sequence转换为Card数组 + let result: Card[] = [] + if (tmp.length > minLength) { + for (let point of tmp) { + if (special && point === special.number) { + result.push(special) + } else { + result.push(pointMap.get(point)[0]) + } + } + } + return result +} + +function parseCheat(this: any, cardArr: Card[], rate: number, eatCard?: Card) { + let minCount = eatCard ? new GameEnv().otherEatCount : new GameEnv().selfEatCount + if (cardArr.length >= minCount && rate > 0) { + let random = getRandom(0, 100) + robotLog(`check cheat ^_^ random: ${random}, rate: ${rate}`) + if (random <= rate) { + let max = Math.min(minCount+2, cardArr.length + 1) + let randomCount = getRandom(minCount, max) + let resultCards: Card[] = cardArr.randomGet(randomCount) + let sameVal = false + if (getRandom(0, 100) > 50) { + sameVal = true + } + let resultNums = [] + if (eatCard && (eatCard.type == CardType.general || eatCard.type == CardType.variable_unit)) { + if (sameVal) { + for (let _c of resultCards) { + resultNums.push(eatCard.number) + } + } else { + resultNums.push(eatCard.number) + for (let _c of resultCards) { + let nextU: number = Math.max.apply(this, resultNums) + 1 + let nextD: number = Math.min.apply(this, resultNums) - 1 + if (getRandom(0, 100) > 50 ) { + if (nextU <= 10) { + resultNums.push(nextU) + } else { + resultNums.push(nextD) + } + } else { + if (nextD > 0) { + resultNums.push(nextD) + } else { + resultNums.push(nextU) + } + } + } + } + resultCards.push(eatCard) + } else { + if (sameVal) { + for (let i = 0; i < resultCards.length; i++) { + resultNums.push(resultCards[0].number) + } + } else { + resultNums.push(resultCards[0].number) + for (let i = 1; i < resultCards.length; i++) { + let nextU: number = Math.max.apply(this, resultNums) + 1 + let nextD: number = Math.min.apply(this, resultNums) - 1 + if (getRandom(0, 100) > 50 ) { + if (nextU <= 10) { + resultNums.push(nextU) + } else { + resultNums.push(nextD) + } + } else { + if (nextD > 0) { + resultNums.push(nextD) + } else { + resultNums.push(nextU) + } + } + } + } + } + return {cards: resultCards, nums: resultNums} + } + } + const oneCardArr: Card[] = cardArr.randomGet(1) + return {cards: oneCardArr } +} + + let assistantUtil = { /** * 检查是否可以吃牌或出连牌 * @param cardArr 待检查的卡组 - * @param card 目标牌 + * @param eatCard 可以吃的牌 + * @param rate 作弊机率 */ - checkDiscard({cardArr, card, rate}: {cardArr: Card[], card?: Card, rate?: number}): {cards: Card[], nums?: number[]} { - let maxCount = card ? new GameEnv().otherEatCount : new GameEnv().selfEatCount + checkDiscard({cardArr, eatCard }: {cardArr: Card[], eatCard?: Card}): any{ let pointMap: Map = new Map() - let cardIdSet: Set = new Set() + let cardPointSet: Set = new Set() for (let c of cardArr) { if (!(c.type == CardType.general || c.type == CardType.variable_unit)) { continue } pushMapVal(pointMap, c.number, c) - cardIdSet.add(c.number) + cardPointSet.add(c.number) } - if (card && (card.type == CardType.general || card.type == CardType.variable_unit)) { - pushMapVal(pointMap, card.number, card) - cardIdSet.add(card.number) + if (eatCard && (eatCard.type == CardType.general || eatCard.type == CardType.variable_unit)) { + pushMapVal(pointMap, eatCard.number, eatCard) + cardPointSet.add(eatCard.number) } - let fetched = false - let result: Card[] = [] - // 优先出对子 - for (let [point, arr] of pointMap) { - if (card) { - if (point == card.number && arr.length >= maxCount) { - fetched = true - result = arr - break - } - } else { - if (arr.length >= maxCount) { - fetched = true - result = arr - break - } - } + let cardPoints = [...cardPointSet] + cardPoints.sort((a, b) => a - b) + let eatPairs = seekPairWithSpecial(pointMap, eatCard) + let normalPairs = seekPairNoSpecial(pointMap, eatCard) + let eatSecs = seekSeq(pointMap, cardPoints, true, eatCard) + let normalSecs = seekSeq(pointMap, cardPoints, false, eatCard) + const selfEat = normalPairs.length > 0 || normalSecs.length > 0 + const otherEat = eatPairs.length > 0 || eatSecs.length > 0 + return { + selfEat, + otherEat, + eatPairs, + normalPairs, + eatSecs, + normalSecs } - if (fetched) { - return {cards: result} + }, + /** + * 出牌 + */ + discard({cardArr, eatCard, rate, noEatRate, noTripleRate}: + {cardArr: Card[], + eatCard?: Card, + rate: number, + noEatRate: number, + noTripleRate: number}): {cards: Card[], nums?: number[]} { + const { + selfEat, + otherEat, + eatPairs, + normalPairs, + eatSecs, + normalSecs + } = this.checkDiscard({cardArr, eatCard}) + robotLog(`discard check result: selfEat: ${selfEat}, otherEat: ${otherEat}`) + let results: Card[] = [] + let random = getRandom(0, 100) + if (otherEat && random <= noEatRate) { + robotLog(`cat eat, but random(${random}) less then cfg(${noEatRate})`) + } else if (otherEat && random > noEatRate) { + results = eatPairs.length > 0? eatPairs : eatSecs + robotLog(`eat card random(${random}) cfg(${noEatRate}) ${results.map(o=>o.number)}`) } - - 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[tmp.length - 1] + 1) { - tmp.length = 0 - } - tmp.push(cur) - } - if (card) { - if (tmp.indexOf(card.number) >= 0 && tmp.length >= maxCount) { - break - } - } else { - if (tmp.length >= maxCount) { - break - } - } + random = getRandom(0, 100) + if (selfEat && results.length == 0 && random <= noTripleRate) { + robotLog(`can hu, but random(${random}) less then cfg(${noTripleRate})`) + } if (selfEat && results.length == 0 && random > noTripleRate) { + results = normalPairs.length > 0 ? normalPairs : normalSecs + robotLog(`hu card random(${random}) cfg(${noTripleRate}) ${results.map(o=>o.number)}`) } - - if (tmp.length >= maxCount) { - 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 {cards: result} - } else { - if (cardArr.length > maxCount && rate > 0) { - let random = getRandom(0, 100) - robotLog(`check cheat ^_^ random: ${random}, rate: ${rate}`) - if (random <= rate) { - let max = Math.min(maxCount+2, cardArr.length + 1) - let randomCount = getRandom(maxCount, max) - let resultCards: Card[] = cardArr.randomGet(randomCount) - let sameVal = false - if (getRandom(0, 100) > 50) { - sameVal = true - } - let resultNums = [] - if (card && (card.type == CardType.general || card.type == CardType.variable_unit)) { - if (sameVal) { - for (let _c of resultCards) { - resultNums.push(card.number) - } - } else { - resultNums.push(card.number) - for (let _c of resultCards) { - let nextU: number = Math.max.apply(this, resultNums) + 1 - let nextD: number = Math.min.apply(this, resultNums) - 1 - if (getRandom(0, 100) > 50 ) { - if (nextU <= 10) { - resultNums.push(nextU) - } else { - resultNums.push(nextD) - } - } else { - if (nextD > 0) { - resultNums.push(nextD) - } else { - resultNums.push(nextU) - } - } - } - } - resultCards.push(card) - } else { - if (sameVal) { - for (let i = 0; i < resultCards.length; i++) { - resultNums.push(resultCards[0].number) - } - } else { - resultNums.push(resultCards[0].number) - for (let i = 1; i < resultCards.length; i++) { - let nextU: number = Math.max.apply(this, resultNums) + 1 - let nextD: number = Math.min.apply(this, resultNums) - 1 - if (getRandom(0, 100) > 50 ) { - if (nextU <= 10) { - resultNums.push(nextU) - } else { - resultNums.push(nextD) - } - } else { - if (nextD > 0) { - resultNums.push(nextD) - } else { - resultNums.push(nextU) - } - } - } - } - return {cards: resultCards, nums: resultNums} - } - } - } - return {cards: [cardArr.randomOne()]} + if (results.length == 0) { + results = cardArr.randomGet(1) } + if (!selfEat && !otherEat) { + return parseCheat(cardArr, rate, eatCard) + } + return {cards: results} }, /** * 随机获取敌对玩家