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

302 lines
10 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,
fetchLevelCfg,
fetchSinglePuzzleType,
getRank,
startGame,
transformRecord,
updateSingleRank
} from '../../services/GameLogic'
import { Shop, validShopId } from '../../models/shop/Shop'
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, '没有店铺id或者店铺id格式不正确, 测试使用: 607ff59d4a4e16687a3b7079')
}
const cfg = fetchLevelCfg(level)
let params = {}
let 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, level })
history.members.set(accountid, new PuzzleStatusClass())
for (let record of records) {
history.questions.set(record.id, record.a1)
}
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 } = 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 === answer ? 1 : 0
if (type == 1) { // type = 1 为客户端上报的超时消息, 直接判负
result = 0
}
if (history.status == 9 || history.hasExpired()) {
result = 0
}
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)
if (result == 1) {
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, answer: record, stats: history.members }
if (mode == 1) {
let score = result ? calcPvpScore(history.scheduleKey, statMap.comboCount) : 0
await broadcast(history.room, 'answer', {accountid, result, score})
await updateScore(history.room, [{accountid, score }])
// if (checkSubFinish(history, id)) {
// 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.a1)
}
history.markModified('questions')
await history.save()
return results
}
@role('anon')
@router('post /api/:accountid/puzzle/match')
async joinMultipleGame(req, res) {
const { 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 : 20)
beginSecond = (Math.max(2, beginSecond)) * 1000
/**
* 查找店铺设置, 查看一定时间内是否有要开始的活动
* 如果有, 则读取配置, 加入开始游戏的定时
* 如果没有人请求, 说明没人参加, 也没必要开启定时了
* TODO:: 读取店铺活动配置
*/
let activityId = '1111111'
let roomId = ''
let sessionId = ''
let sessionMatch = ''
let beginTime = 0
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 })
if (history) {
// if (history && !history.hasExpired()) {
beginTime = history.begin
sessionMatch = history.id
if (!history.members.has(accountid)) {
let rsp = await joinRoom({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})
let memberData = new PuzzleStatusClass()
memberData.sessionId = sessionId
history.members.set(accountid, memberData)
history.room = roomId
//TODO: 根据配置赋值
history.total = debug_qcount || 10
history.begin = Date.now() + beginSecond
beginTime = history.begin
history.expire = history.begin + (100 || 90) * 1000
history.type = 1
await history.save()
sessionMatch = history.id
new Schedule().beginSchedule(beginSecond, 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
let {rankList, userRank, rankTotal} = await getRank({shop, level, accountId, mode, skip, limit})
return {rankList, userRank, rankTotal}
}
}