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' 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} 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, 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>( 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 } } }