diff --git a/doc/api.md b/doc/api.md index 5b28b78..ba460f6 100644 --- a/doc/api.md +++ b/doc/api.md @@ -198,4 +198,89 @@ ] ] } +``` + +### 6. 获取测验题目列表 + +1. Method: POST +2. URI: /api/:accountid/exam/list + +| 字段 | 说明 | +| -------- | -------------------------------------- | +| accountid | 帐号id | + +> POST参数 + + +| 字段 | 说明 | +| -------- | -------------------------------------- | +| shop | 店铺id, 比如 607ff59d4a4e16687a3b7079 | + +3. Response: JSON + +```js +{ + session: "6080f330b9655b5c0467ee5a", // 当前局的id,提交答案时必须上报该字段 + timeone: 10, // 每题最多回答时间 + records: [{ + "id": "6080f330b9655b5c0467ee5e", // 题目id + "title": "“大丈夫为国捐躯,死而无憾!”这话是谁说的?", // 问题 + "answers": [ // 可选答案 + "刘铭传", + "徐骧", + "刘步蟾", + "刘永福" + ], + "category": "体育-体育", // 类型 + "type": 1, // 题目类型 1: 普通的文字选择题, 2: 图形 + "quality": 1 // 题目难度 +}] +} + +``` + +### 7. 上报测验题目答案 + +1. Method: POST +2. URI: /api/:accountid/exam/answer + +| 字段 | 说明 | +| -------- | -------------------------------------- | +| accountid | 帐号id | + +> POST参数 + + +| 字段 | 说明 | +| -------- | -------------------------------------- | +| id | 题目id | +| session | 当局的id, 从关卡题目列表中获取 | +| answer | 回答的选项 | +| type | 回答类型, 0: 正常, 1: 超时 | + +3. Response: JSON + +```js +{ + result: 1, //答题结果 1: 正确, 0 : 错误 + gameResult: 0, // 当前局游戏结果 0: 未完成, -1: 失败, 1: 胜利 + overtime: 0, // 当前回答是否超时 0: 未超时, 1: 超时 + "stats": { // 当局的状态 + "1111": { + "answer": [ // 每一题的结果 + 1, + 0 + ], + "rightCount": 1, // 答对的数量 + "errorCount": 1, // 答错的数量 + "comboCount": 0, // 当前连续答对的数量 + "maxCombo": 1, // 当局连续答对的最大数量 + "score": 10, // 当前得分 + "star": 1, // 当局胜利后获得的星星 + "timeLeft": 1, // 当局剩余时间 + "gameResult": 0, // 当局的游戏结果, 单人的话和上一层gameResult相同 + "timeLast": 1620973155307 //上次回答时间 + } + } +} ``` \ No newline at end of file diff --git a/src/api/controllers/exam.controller.ts b/src/api/controllers/exam.controller.ts new file mode 100644 index 0000000..ec71a37 --- /dev/null +++ b/src/api/controllers/exam.controller.ts @@ -0,0 +1,132 @@ +import BaseController from '../../common/base.controller' +import { role, router } from '../../decorators/router' +import { isObjectId } from '../../utils/string.util' +import { ZError } from '../../common/ZError' +import { ShopExam } from '../../models/shop/ShopExam' +import { Puzzle } from '../../models/content/Puzzle' +import { + PuzzleSession, + PuzzleStatusClass +} from '../../models/match/PuzzleSession' +import { + calcExamScore, + getRank, + transformRecord, updateExamRank, updateSingleRank +} from '../../services/GameLogic' + +class ExamController extends BaseController { + @role('anon') + @router('post /api/:accountid/exam/list') + async list(req, res) { + let { shop, accountid } = req.params + if (!shop || !isObjectId(shop)) { + throw new ZError(10, '没有店铺id或者店铺id格式不正确, 测试使用: 607ff59d4a4e16687a3b7079') + } + const now = Date.now() + let record = await ShopExam.findOne({shop, beginTime: {$lte: now}, endTime: {$gte: now}, active: true, deleted: false}) + if (!record) { + throw new ZError(11, '暂时没有可参加的测试') + } + let params = {} + if (record.qtypes && record.qtypes.length > 0) { + params = {$or: [{tag: {$in: record.qtypes}}, {sub_tag: {$in: record.qtypes}}]} + } + let questions = await Puzzle.randomQuestions(params, record.qcount) + let history = new PuzzleSession({ shop, level: 0}) + let stat = new PuzzleStatusClass() + stat.timeLast = now + history.members.set(accountid, stat) + for (let record of questions) { + history.questions.set(record.id, record.a1) + } + history.expire = Date.now() + (record.qcount * record.timeone || 90) * 1000 + history.type = 2 + history.total = record.qcount + history.timeone = record.timeone + history.difficultyMode = 0 + history.qtypes = record.qtypes + await history.save() + const results = transformRecord(questions) + return { + session: history.id, + records: results, + timeone: record.timeone + } + + } + @role('anon') + @router('post /api/:accountid/exam/answer') + async report(req, res) { + /** + * type 0:正常 1: 超时 + */ + let { id, answer, type, session, accountid } = 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.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 statMap = history.members.get(accountid) + if (statMap.answer.has(id)) { + throw new ZError(15, 'current question already answered') + } + let time = (history.timeone* 1000 - (Date.now() - statMap.timeLast)) / 1000 + let record = history.questions.get(id) + let result = record === answer ? 1 : 0 + let overtime = 0 + if (type == 1) { // type = 1 为客户端上报的超时消息, 直接判负 + result = 0 + overtime = 1 + } else if (time < 0){ + result = 0 + overtime = 1 + } + 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 + + let score = result ? calcExamScore(time, statMap.comboCount) : 0 + statMap.score += score + history.markModified('members') + await history.save() + let gameResult = 0 + let rspData: any = { result, answer: record, stats: history.members, overtime } + if (statMap.answer.size >= history.questions.size) { + gameResult = 1 + } + if (gameResult === 1) { + history.status = 9 + let rankObj = { + shop: history.shop, + level: 'exam', + accountId: accountid, + score: statMap.score, + mode: 0, + session: history.id + } + await history.save() + await updateExamRank(rankObj) + let {rankList, userRank } = await getRank({shop: history.shop, level: 'exam', accountId: accountid, mode: 0, skip: 0, limit: 20}) + rspData.rankList = rankList + rspData.userRank = userRank + } + rspData.gameResult = gameResult + return rspData + } +} diff --git a/src/api/controllers/puzzle.controller.ts b/src/api/controllers/puzzle.controller.ts index e80bff5..e0788f5 100644 --- a/src/api/controllers/puzzle.controller.ts +++ b/src/api/controllers/puzzle.controller.ts @@ -27,9 +27,7 @@ import { transformRecord, updateSingleRank } from '../../services/GameLogic' -import { ObjectId } from 'bson' import { isObjectId } from '../../utils/string.util' -import { getAccountScore } from '../../services/Rank' class PuzzleController extends BaseController { @@ -103,7 +101,7 @@ class PuzzleController extends BaseController { } let record = history.questions.get(id) let result = record === answer ? 1 : 0 - if (type == 1) { + if (type == 1) { // type = 1 为客户端上报的超时消息, 直接判负 result = 0 } if (history.status == 9 || history.hasExpired()) { @@ -113,7 +111,7 @@ class PuzzleController extends BaseController { 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++ diff --git a/src/models/match/PuzzleSession.ts b/src/models/match/PuzzleSession.ts index f1179e8..a68069a 100644 --- a/src/models/match/PuzzleSession.ts +++ b/src/models/match/PuzzleSession.ts @@ -55,6 +55,12 @@ export class PuzzleStatusClass { @prop() timeLeft: number + /** + * 最后一次答题时间 + * @type {number} + */ + @prop() + timeLast: number /** * 游戏结果 @@ -90,6 +96,7 @@ export class PuzzleSessionClass extends BaseModule { * 类型 * 0: 单人 * 1: 多人 + * 2: 测验 * @type {number} */ @prop({default: 0}) @@ -117,6 +124,13 @@ export class PuzzleSessionClass extends BaseModule { @prop() public total: number + /** + * 每一题回答时间 + * @type {number} + */ + @prop() + public timeone: number + /** * 比赛状态 * 0: 未开始答题 @@ -148,6 +162,7 @@ export class PuzzleSessionClass extends BaseModule { } public get scheduleKey() { + // @ts-ignore return this.room + this._id } } diff --git a/src/models/shop/ShopExam.ts b/src/models/shop/ShopExam.ts index 66aea19..63ee366 100644 --- a/src/models/shop/ShopExam.ts +++ b/src/models/shop/ShopExam.ts @@ -64,7 +64,7 @@ export class ShopExamClass extends BaseModule { * @type {number} */ @prop({default: 5}) - public timePer: number + public timeone: number /** * 活动正式开始时间, 从0点开始 diff --git a/src/services/GameLogic.ts b/src/services/GameLogic.ts index 44d2c65..387c8df 100644 --- a/src/services/GameLogic.ts +++ b/src/services/GameLogic.ts @@ -114,6 +114,10 @@ export async function sendOneQuestion(history: any) { }, 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) @@ -135,7 +139,7 @@ export function rankKey(shop: string, level: number|string, mode: number) { */ export async function updateSingleRank({shop, level, accountId, score, session, mode } : {shop: string, - level: number, + level: number | string, accountId: string, score: number, mode: number, @@ -155,6 +159,17 @@ export async function updateSingleRank({shop, level, accountId, score, session, } +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,