增加测验的相关接口

This commit is contained in:
zhl 2021-05-14 14:27:37 +08:00
parent 8cf133b200
commit e9a4a4d552
6 changed files with 251 additions and 6 deletions

View File

@ -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 //上次回答时间
}
}
}
```

View File

@ -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
}
}

View File

@ -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++

View File

@ -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
}
}

View File

@ -64,7 +64,7 @@ export class ShopExamClass extends BaseModule {
* @type {number}
*/
@prop({default: 5})
public timePer: number
public timeone: number
/**
* , 0

View File

@ -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,