corgi/src/services/GameLogic.ts
2021-05-14 14:27:37 +08:00

293 lines
8.9 KiB
TypeScript

import {
beginGame,
endGame,
sendQuestion,
updateRound,
updateScore
} from './WsSvr'
import { Puzzle } from '../models/content/Puzzle'
import { Schedule } from '../clock/Schedule'
import { BaseConst } from '../constants/BaseConst'
import { QCategoryCache } from './QCategoryCache'
import { PuzzleSession } from '../models/match/PuzzleSession'
import { GameEnv } from '../config/GameEnv'
import { Shop } from '../models/shop/Shop'
import { mission_vo } from '../config/parsers/mission_vo'
import { ShopActivity } from '../models/shop/ShopActivity'
import { getAccountRank, getRankList, updateRank } from './Rank'
import { PuzzleRank } from '../models/match/PuzzleRank'
export function transformRecord(records: any[]) {
return records.map(o => {
let answers = []
for (let i = 1; i <= 4; i++) {
if (o[`a${ i }`]) {
answers.push(o[`a${ i }`])
}
}
answers.randomSort()
let type = new QCategoryCache().getType(o.tag)
let subType = new QCategoryCache().getType(o.sub_tag)
return {
id: o._id,
title: o.question,
answers,
type: 1,
category: type + '-' + subType,
quality: o.quality
}
})
}
/**
* 检查是否所有人都已经答了当前题目
*
* @param history
* @param {string} qid
* @return {boolean}
*/
export function checkSubFinish(history: any, qid: string) {
for (let [key, val] of history.members) {
if (!val.answer?.has(qid)) {
return false
}
}
return true
}
/**
* 开始游戏
* @param {string} roomId
* @param sessionId
* @return {Promise<void>}
*/
export async function startGame(roomId: string, sessionId: string) {
let history = await PuzzleSession.findById(sessionId)
let records = await Puzzle.randomQuestions({status: 1, is_hide: 0, deleted: 0, dp: 1}, history.total)
await beginGame(roomId, {})
for (let record of records) {
history.questions.set(record.id, record.a1)
}
history.markModified('questions')
await history.save()
new Schedule().beginSchedule(BaseConst.FIST_QUESTION_DELAY, async function () {
await sendOneQuestion(history)
}, history.scheduleKey)
}
export async function sendOneQuestion(history: any) {
const roomId = history.room
new Schedule().stopSchedule(history.scheduleKey)
if (history.current >= history.questions.size) {
console.log('match over')
history.status = 9
await history.save()
await endGame(roomId, {closeTime: BaseConst.ROOM_AUTO_CLOSE_TIME})
return
}
const questions:string[] = Array.from(history.questions.keys())
let qid: string = questions[history.current]
let record = await Puzzle.findById(qid)
let qdata: any = transformRecord([record])[0]
qdata.no = history.current
await updateRound(roomId, history.current)
await sendQuestion(roomId, qdata)
history.current++
await history.save()
new Schedule().beginSchedule(new GameEnv().pvpQuestionInterval, async function () {
let subHistory = await PuzzleSession.findById(history.id)
let datas = []
for (let [accountid, data] of subHistory.members) {
if (!data.answer.has(qid)) {
data.answer.set(qid, 0)
data.errorCount++
data.comboCount = 0
datas.push({ accountid, score: 0 })
}
}
subHistory.markModified('members')
await subHistory.save()
await updateScore(subHistory.room, datas)
await sendOneQuestion(subHistory)
}, history.scheduleKey)
}
export function calcExamScore(time: number, combo: number) {
const cfg = new GameEnv()
return cfg.pvpBaseScore + time / 100 * cfg.pvpTimeRate + combo * cfg.pvpComboRate
}
export function calcPvpScore(timeKey: string, combo: number) {
const time = new Schedule().getLeftTime(timeKey)
const cfg = new GameEnv()
return cfg.pvpBaseScore + time / 100 * cfg.pvpTimeRate + combo * cfg.pvpComboRate
}
export function rankKey(shop: string, level: number|string, mode: number) {
return `${shop}_${mode}_${level}`
}
/**
* 更新排行榜
* @param {string} shop 店铺id
* @param {number} level 关卡id
* @param {string} accountId 帐号id
* @param {number} score 分数
* @param {string} session
* @param {number} mode 模式: 0: 单人 1: pvp
* @return {Promise<void>}
*/
export async function updateSingleRank({shop, level, accountId, score, session, mode }
: {shop: string,
level: number | string,
accountId: string,
score: number,
mode: number,
session: string}) {
const key = rankKey(shop, level, mode)
await updateRank(accountId, score, key)
let record = (await PuzzleRank.findOrCreate({shop, level, accountId, mode})).doc
if (record.score < score) {
record.score = score
record.session = session
await record.save()
}
let scoreTotal = await PuzzleRank.fetchTotalScore(shop, accountId, mode)
const totalKey = rankKey(shop, 'total', mode)
await updateRank(accountId, scoreTotal, totalKey)
}
export async function updateExamRank({shop, level, accountId, score, session, mode }
: {shop: string,
level: number | string,
accountId: string,
score: number,
mode: number,
session: string}) {
const totalKey = rankKey(shop, 'exam', mode)
await updateRank(accountId, score, totalKey)
}
export async function getRank({shop, level, accountId, mode, skip, limit }
: {shop: string,
level: number | string,
accountId: string,
mode: number, skip: number, limit: number}) {
// let records = await PuzzleRank.find({shop, level, mode}).limit(limit).skip(skip).sort({'score': -1})
const key = rankKey(shop, level, mode)
let datas: any = await getRankList(skip, limit, key)
// let scoreMap: Map<any, number> = new Map()
let rankList: any[][] = []
for (let i = 0, l = datas.length; i < l; i += 2) {
// scoreMap.set(datas[i], datas[i + 1] << 0)
rankList.push([datas[i], datas[i + 1] << 0])
}
let userRank = (await getAccountRank(accountId, key)) || 999
return {rankList, userRank}
}
/**
* 单人模式检查游戏是否结束
* @param history
* @param {string} accountId
* @return {number} 0: 未结束, -1: 失败, 1: 胜利
*/
export function checkSingleFinish(history: any, accountId: string): number {
let result = 0
let stat = history.members.get(accountId)
if (history.hasExpired()) {
result = -1
} else {
let cfg = fetchLevelCfg(history.level)
if (stat.rightCount >= cfg.enemy) {
result = 1
} else if (stat.errorCount >= cfg.hp) {
result = -1
}
}
if (result !== 0) {
stat.gameResult = result
}
return result
}
/**
* 计算单人模式的得分
* @return {number}
*/
export function calcSingleScore(history: any, accountId: string) {
let cfgLevel = fetchLevelCfg(history.level)
let time = cfgLevel.time - (Date.now() - history.createdAt.getTime()) / 1000
let stat = history.members.get(accountId)
let combo = stat.maxCombo
let hp = cfgLevel.hp - stat.errorCount
hp = hp < 0 ? 0 : hp
time = time < 0 ? 0 : time
let cfg = new GameEnv()
let score = time * cfg.singleTimeRate + hp * cfg.singleHpRate + combo * cfg.singleComboRate;
(history.difficultyMode) && (score *= cfg.singleLowLvlRate)
stat.score = score
let star = 0;
(time >= cfgLevel.timestar) && star++;
(stat.maxCombo >= cfgLevel.enemystar) && star++;
(hp >= cfgLevel.hpstar) && star ++;
stat.star = star
stat.timeLeft = time
return { score, star }
}
/**
* 确定pvp模式题目类型
* @param {string} shopId
* @param {string} aid 活动配置
* @return {Promise<void>}
*/
export async function fetchPvpPuzzleType({shopId, aid} : {shopId: string, aid?: string}) {
//{$or:[{tag: {$in: ['a', 'SS']}}, {sub_tag: {$in: ['a', 'SS']}}]}
let typeArr: string[] = []
if (aid) {
let actData = await ShopActivity.findById(aid)
if (actData?.qtypes?.length > 0) {
typeArr = actData.qtypes
}
}
if (typeArr.length === 0 ) {
let shop = await Shop.findById(shopId)
if (shop?.qtypes?.length > 0) {
typeArr = shop.qtypes
}
}
// let params = {$or: [{tag: {$in: typeArr}}, {sub_tag: {$in: typeArr}}]}
return typeArr
}
/**
* 获取单人模式题目类型
* @param {string} shopId
* @param {mission_vo} levelCfg
* @return {Promise<void>}
*/
export async function fetchSinglePuzzleType({shopId, levelCfg} : {shopId: string, levelCfg?: mission_vo}) {
let typeArr: string[] = []
let shop = await Shop.findById(shopId)
if (shop?.qtypes?.length > 0) {
typeArr = shop.qtypes
}
if (typeArr.length === 0 && levelCfg?.keyID) {
typeArr = levelCfg.keyID.split(':')
}
return typeArr
}
/**
* 获取关卡配置信息
* @param {number} level
*/
export function fetchLevelCfg(level: number) {
const cfgs: mission_vo[] = Array.from(global.$cfg.get(BaseConst.MISSION).values())
return cfgs.find(o => o.number == level) || cfgs[cfgs.length - 1]
}