293 lines
8.9 KiB
TypeScript
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]
|
|
}
|