corgi/src/api/controllers/puzzle.controller.ts

397 lines
13 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/user/GameUser'
import { isObjectId } from '../../utils/string.util'
import { SINGLE_HELP_COUNT } from '../../constants/BaseConst'
import { md5 } from '../../utils/security.util'
import { Coupon } from '../../models/shop/Coupon'
class PuzzleController extends BaseController {
@role('anon')
@router('get /api/test')
async test(req) {
const { id, count } = req.params
// const res = await Coupon.updateCount(id, count)
return {}
}
@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} level:${level} 没有店铺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: accountId, result, score })
await updateScore(history.room, [{ accountid: 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,
}
const { over } = await updateSingleRank(rankObj)
rspData.over = over
}
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: 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 }
}
@role('anon')
@router('post /api/:accountId/puzzle/help')
async help(req) {
let { id, session, accountId, sessionid } = req.params
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.hasExpired()) {
throw new ZError(18, 'match has expired')
}
if (history.type !== 0) {
throw new ZError(18, 'only available in single mode')
}
if (history.helpCount >= SINGLE_HELP_COUNT) {
throw new ZError(17, 'reach max count')
}
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)
history.helpCount += 1
await history.save()
let hashStr = `${session}_${id}_${record.answers[0]}_${sessionid}`
let hash = md5(hashStr)
return { answer: hash }
}
}