修改机器人出牌逻辑, 增加放弃吃牌和胡牌的机制

This commit is contained in:
zhl 2021-03-19 15:23:42 +08:00
parent 984ad01fb3
commit 498a81a3b7
7 changed files with 313 additions and 177 deletions

View File

@ -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
}
}
}

View File

@ -60,7 +60,7 @@ export class RoomOptions {
* @param opt
*/
public multipleEat(opt?: any) {
return 0
return 1
}
/**

View File

@ -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) {

View File

@ -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) {

View File

@ -44,12 +44,10 @@ export class BeginGameCommand extends Command<CardGameState, {}> {
}
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 })
}
}

View File

@ -113,7 +113,8 @@ export class SelectPetCommand extends Command<CardGameState, {
let time = this.room.battleMan.useCard(data)
await this.delay(time)
const multipEat = !!this.state.rules.get(RULE_MULTIPLEEAT)
if (multipEat && assistantUtil.checkDiscard({cardArr: [...player.cards.values()]}).cards.length > 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}`)

View File

@ -24,169 +24,246 @@ function pushMapVal(map: Map<number, Card[]>, key: number, value: Card) {
}
}
/**
*
* @param {Map<number, Card[]>} pointMap
* @param {number} special
* @return {Card[]}
*/
function seekPairWithSpecial(pointMap: Map<number, Card[]>, 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<number, Card[]>} pointMap
* @param {number} special
* @return {Card[]}
*/
function seekPairNoSpecial(pointMap: Map<number, Card[]>, 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<number, Card[]>, 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<number, Card[]> = new Map()
let cardIdSet: Set<number> = new Set()
let cardPointSet: Set<number> = 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}
},
/**
*