367 lines
12 KiB
TypeScript
367 lines
12 KiB
TypeScript
import BaseController from '../../common/base.controller'
|
|
import { role, router } from '../../decorators/router'
|
|
import { Puzzle } from '../../models/content/Puzzle'
|
|
import { PuzzleSession, PuzzleStatusClass } from '../../models/match/PuzzleSession'
|
|
import { ZError } from '../../common/ZError'
|
|
import { broadcast, createRoom, joinRoom, updateScore } from '../../services/WsSvr'
|
|
import { RoomState } from '../../services/RoomState'
|
|
import { retry } from '../../utils/promise.util'
|
|
import { RoomLockErr } from '../../common/RoomLockErr'
|
|
import { Schedule } from '../../clock/Schedule'
|
|
import {
|
|
calcPvpScore,
|
|
calcSingleScore,
|
|
checkSingleFinish,
|
|
checkSubFinish,
|
|
fetchLevelCfg,
|
|
fetchSinglePuzzleType,
|
|
getRank,
|
|
sendOneQuestion,
|
|
startGame,
|
|
transformRecord,
|
|
updateSingleRank,
|
|
} from '../../services/GameLogic'
|
|
import { Shop, validShopId } from '../../models/shop/Shop'
|
|
import { ShopActivity } from '../../models/shop/ShopActivity'
|
|
import { GameUser } from '../../models/GameUser'
|
|
import { isObjectId } from '../../utils/string.util'
|
|
|
|
class PuzzleController extends BaseController {
|
|
@role('anon')
|
|
@router('get /api/test')
|
|
async test(req) {
|
|
// try{
|
|
// console.time('ss')
|
|
// const nanoid = customAlphabet('2345678abcdefghjkmnpqrstwxy', 10)
|
|
// for (let i = 0; i < 100; i++) {
|
|
// console.log(nanoid())
|
|
// }
|
|
// console.timeEnd('ss')
|
|
// } catch (err) {
|
|
// }
|
|
let shop = await Shop.find({}, { _id: 1 }).limit(1)
|
|
return shop[0]
|
|
}
|
|
|
|
@role('anon')
|
|
@router('post /api/:accountid/puzzle/list')
|
|
async list(req, res) {
|
|
let { shop, level, accountid, type } = req.params
|
|
level = +level || 1
|
|
if (!shop || !validShopId(shop)) {
|
|
throw new ZError(10, `type: ${type} 没有店铺id或者店铺id格式不正确, ${shop}`)
|
|
}
|
|
const cfg = fetchLevelCfg(level)
|
|
let params = {}
|
|
let { shopId, typeArr } = await fetchSinglePuzzleType({ shopId: shop, levelCfg: cfg })
|
|
if (typeArr?.length > 0) {
|
|
params = { $or: [{ tag: { $in: typeArr } }, { sub_tag: { $in: typeArr } }] }
|
|
}
|
|
let record1 = await Puzzle.randomQuestions(Object.assign(params, { quality: 1 }), 5)
|
|
let record2 = await Puzzle.randomQuestions(Object.assign(params, { quality: 2 }), 5)
|
|
let record3 = await Puzzle.randomQuestions(Object.assign(params, { quality: 3 }), 5)
|
|
const records = record1.concat(record2).concat(record3)
|
|
let history = new PuzzleSession({ shop: shopId, level })
|
|
history.members.set(accountid, new PuzzleStatusClass())
|
|
for (let record of records) {
|
|
history.questions.set(record.id, record.compactRecord(true))
|
|
}
|
|
history.expire = Date.now() + (cfg.time || 90) * 1000
|
|
history.type = 0
|
|
history.difficultyMode = type || 0
|
|
history.qtypes = typeArr
|
|
await history.save()
|
|
const results = transformRecord(records)
|
|
return {
|
|
session: history.id,
|
|
records: results,
|
|
}
|
|
}
|
|
|
|
@role('anon')
|
|
@router('post /api/:accountid/puzzle/answer')
|
|
async report(req, res) {
|
|
let { id, answer, type, session, accountid, mode, debug } = req.params
|
|
mode = mode || 0
|
|
if (!id || !session) {
|
|
throw new ZError(11, 'param mismatch')
|
|
}
|
|
let history = await PuzzleSession.findById(session)
|
|
if (!history) {
|
|
throw new ZError(13, 'not found match info')
|
|
}
|
|
if (!history.members.has(accountid)) {
|
|
throw new ZError(14, 'not in current match')
|
|
}
|
|
if (!history.questions.has(id)) {
|
|
throw new ZError(16, 'current question not in current match')
|
|
}
|
|
let record = history.questions.get(id)
|
|
let result = record.answers.indexOf(answer)
|
|
if (type == 1) {
|
|
// type = 1 为客户端上报的超时消息, 直接判负
|
|
result = -1
|
|
}
|
|
if (history.status == 9 || history.hasExpired()) {
|
|
result = -1
|
|
}
|
|
let statMap = history.members.get(accountid)
|
|
if (statMap.answer.has(id)) {
|
|
throw new ZError(15, 'current question already answered')
|
|
}
|
|
statMap.timeLast = Date.now()
|
|
statMap.answer.set(id, result)
|
|
statMap.total++
|
|
if (record.type !== 3) {
|
|
if (result == 0) {
|
|
statMap.rightCount++
|
|
statMap.comboCount++
|
|
statMap.maxCombo = Math.max(statMap.maxCombo, statMap.comboCount)
|
|
} else {
|
|
statMap.errorCount++
|
|
statMap.comboCount = 0
|
|
}
|
|
}
|
|
history.status = 1
|
|
history.markModified('members')
|
|
let gameResult = 0
|
|
await history.save()
|
|
let rspData: any = {
|
|
result: record.type === 3 ? 1 : result === 0 ? 1 : 0,
|
|
answer: record.type === 3 ? '' : record.answers[0],
|
|
stats: history.members,
|
|
}
|
|
if (mode == 1) {
|
|
let score = result === 0 ? calcPvpScore(history.scheduleKey, statMap.comboCount) : 0
|
|
if (record.type === 3) {
|
|
score = 0
|
|
}
|
|
await broadcast(history.room, 'answer', { accountid, result, score })
|
|
await updateScore(history.room, [{ accountid, score }])
|
|
|
|
if (checkSubFinish(history, id)) {
|
|
if (new Schedule().getLeftTime(history.scheduleKey) > 1000) {
|
|
new Schedule().beginSchedule(
|
|
1000,
|
|
async function () {
|
|
await sendOneQuestion(history)
|
|
},
|
|
history.scheduleKey,
|
|
)
|
|
}
|
|
}
|
|
if (debug) {
|
|
await sendOneQuestion(history)
|
|
}
|
|
} else if (mode == 0) {
|
|
gameResult = checkSingleFinish(history, accountid)
|
|
if (gameResult) {
|
|
history.status = 9
|
|
if (gameResult === 1) {
|
|
let { score } = calcSingleScore(history, accountid)
|
|
let rankObj = {
|
|
shop: history.shop,
|
|
level: history.level,
|
|
accountId: accountid,
|
|
score,
|
|
mode: 0,
|
|
session: history.id,
|
|
}
|
|
await updateSingleRank(rankObj)
|
|
}
|
|
history.markModified('members')
|
|
await history.save()
|
|
let { rankList, userRank } = await getRank({
|
|
shop: history.shop,
|
|
level: history.level,
|
|
accountId: accountid,
|
|
mode: 0,
|
|
skip: 0,
|
|
limit: 20,
|
|
})
|
|
rspData.rankList = rankList
|
|
rspData.userRank = userRank
|
|
}
|
|
}
|
|
rspData.gameResult = gameResult
|
|
return rspData
|
|
}
|
|
|
|
@role('anon')
|
|
@router('post /api/:accountid/puzzle/more')
|
|
async moreLevelQuestions(req, res) {
|
|
let { session, accountid, count, quality } = req.params
|
|
count = count ? +count : 10
|
|
if (!session) {
|
|
throw new ZError(11, 'param mismatch')
|
|
}
|
|
let history = await PuzzleSession.findById(session)
|
|
if (!history) {
|
|
throw new ZError(13, 'not found match info')
|
|
}
|
|
if (history.status == 9 || history.hasExpired()) {
|
|
history.status = 9
|
|
await history.save()
|
|
throw new ZError(17, 'match end')
|
|
}
|
|
if (!history.members.has(accountid)) {
|
|
throw new ZError(14, 'not in current match')
|
|
}
|
|
let ids = Array.from(history.questions.keys())
|
|
let options: any = { _id: { $nin: ids } }
|
|
if (quality) {
|
|
options.quality = quality
|
|
}
|
|
if (history.qtypes.length > 0) {
|
|
Object.assign(options, { $or: [{ tag: { $in: history.qtypes } }, { sub_tag: { $in: history.qtypes } }] })
|
|
}
|
|
let records = await Puzzle.randomQuestions(options, count)
|
|
const results = transformRecord(records)
|
|
for (let record of records) {
|
|
history.questions.set(record.id, record.compactRecord(true))
|
|
}
|
|
history.markModified('questions')
|
|
await history.save()
|
|
return results
|
|
}
|
|
|
|
@role('anon')
|
|
@router('post /api/:accountid/puzzle/match')
|
|
async joinMultipleGame(req, res) {
|
|
let { shop, aid, accountid, debug_begin_sec, debug_qcount } = req.params
|
|
let data: any = { shop, maxTime: 3600, accountid }
|
|
let beginSecond = debug_begin_sec ? +debug_begin_sec : 10
|
|
beginSecond = Math.max(2, beginSecond) * 1000
|
|
/**
|
|
* 查找店铺设置, 查看一定时间内是否有要开始的活动
|
|
* 如果有, 则读取配置, 加入开始游戏的定时
|
|
* 如果没有人请求, 说明没人参加, 也没必要开启定时了
|
|
* TODO:: 读取店铺活动配置
|
|
*/
|
|
let roomId = ''
|
|
let sessionId = ''
|
|
let sessionMatch = ''
|
|
|
|
if (!aid) {
|
|
throw new ZError(12, 'params no match')
|
|
}
|
|
let activity = await ShopActivity.findById(aid)
|
|
let shopData = await Shop.fetchByID(shop)
|
|
let gameUser = await GameUser.getByAccountID(accountid)
|
|
if (gameUser) {
|
|
data.nickname = gameUser.nickname
|
|
data.avatar = gameUser.avatar
|
|
}
|
|
shop = shopData.id
|
|
if (!activity) {
|
|
throw new ZError(13, 'activity not found')
|
|
}
|
|
let beginTime = activity.currentTime
|
|
const now = Date.now()
|
|
beginTime = beginTime < now ? now : beginTime
|
|
if (now < activity.prepareBeginTime) {
|
|
throw new ZError(14, 'activity not start')
|
|
} else if (now > activity.endTime) {
|
|
throw new ZError(15, 'activity already end')
|
|
}
|
|
let result = new RoomState().isLock(shop)
|
|
try {
|
|
await retry<Promise<string>>(
|
|
async () => {
|
|
if (result) {
|
|
throw new RoomLockErr('')
|
|
}
|
|
new RoomState().lock(shop)
|
|
let history = await PuzzleSession.findOne({ shop, status: { $in: [0, 1] }, type: 1, activityId: aid })
|
|
// if (history) {
|
|
if (history && !history.hasExpired()) {
|
|
beginTime = history.begin
|
|
sessionMatch = history.id
|
|
if (!history.members.has(accountid)) {
|
|
let rsp = await joinRoom(Object.assign(data, { roomId: history.room }))
|
|
if (rsp.status != 200) {
|
|
new RoomState().unlock(shop)
|
|
throw new ZError(11, 'error create room')
|
|
}
|
|
let memberData = new PuzzleStatusClass()
|
|
sessionId = rsp.data?.sessionId
|
|
memberData.sessionId = sessionId
|
|
history.members.set(accountid, memberData)
|
|
history.markModified('members')
|
|
await history.save()
|
|
new RoomState().unlock(shop)
|
|
return (roomId = history.room)
|
|
} else {
|
|
let memberData = history.members.get(accountid)
|
|
sessionId = memberData.sessionId
|
|
roomId = history.room
|
|
new RoomState().unlock(shop)
|
|
return
|
|
}
|
|
} else {
|
|
let rsp = await createRoom(data)
|
|
if (rsp.status != 200) {
|
|
new RoomState().unlock(shop)
|
|
throw new ZError(11, 'error create room')
|
|
}
|
|
roomId = rsp.data?.room?.roomId
|
|
sessionId = rsp.data?.sessionId
|
|
history = new PuzzleSession({ shop, status: 0, type: 1 })
|
|
history.activityId = aid
|
|
let memberData = new PuzzleStatusClass()
|
|
memberData.sessionId = sessionId
|
|
history.members.set(accountid, memberData)
|
|
history.room = roomId
|
|
//TODO: 根据配置赋值
|
|
history.total = debug_qcount || activity.qcount
|
|
if (activity.repeatType === 9) {
|
|
history.begin = now + beginSecond
|
|
} else {
|
|
history.begin = beginTime
|
|
}
|
|
|
|
beginTime = history.begin
|
|
history.expire = history.begin + (activity.endTime - activity.currentTime + 20000)
|
|
history.type = 1
|
|
await history.save()
|
|
sessionMatch = history.id
|
|
new Schedule().beginSchedule(
|
|
beginTime - now,
|
|
async function () {
|
|
await startGame(roomId, history.id)
|
|
},
|
|
shop,
|
|
)
|
|
new RoomState().unlock(shop)
|
|
return roomId
|
|
}
|
|
},
|
|
0,
|
|
[RoomLockErr],
|
|
)
|
|
} catch (err) {
|
|
new RoomState().unlock(shop)
|
|
throw new ZError(12, 'error create room')
|
|
}
|
|
|
|
return { roomId, beginTime, sessionId, session: sessionMatch }
|
|
}
|
|
|
|
@role('anon')
|
|
@router('post /api/:accountId/puzzle/rank')
|
|
async singleRank(req) {
|
|
let { shop, level, accountId, mode, skip, limit } = req.params
|
|
skip = +skip || 0
|
|
limit = +limit || 10
|
|
if (!isObjectId(shop)) {
|
|
let record = await Shop.fetchByID(shop)
|
|
if (!record) {
|
|
throw new ZError(12, 'shop not found')
|
|
}
|
|
shop = record.id
|
|
}
|
|
let { rankList, userRank, rankTotal } = await getRank({ shop, level, accountId, mode, skip, limit })
|
|
return { rankList, userRank, rankTotal }
|
|
}
|
|
}
|