quiz-game-mechanics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseQuiz Game Mechanics
问答游戏机制
This skill covers the core game logic and mechanics for dev-quiz-battle.
本技能涵盖了dev-quiz-battle的核心游戏逻辑与机制。
Step-by-step instructions
分步说明
1. Game Creation
1. 游戏创建
Create a game with initial state:
typescript
export const createGame = mutation({
args: {
creatorId: v.id("users"),
language: v.string(),
maxRounds: v.number(),
},
handler: async (ctx, args) => {
const code = generateUniqueGameCode(6);
const gameId = await ctx.db.insert("games", {
code,
creatorId: args.creatorId,
status: "waiting",
language: args.language,
players: [args.creatorId],
currentRound: 0,
maxRounds: args.maxRounds,
createdAt: Date.now(),
});
return { gameId, code };
},
});创建带有初始状态的游戏:
typescript
export const createGame = mutation({
args: {
creatorId: v.id("users"),
language: v.string(),
maxRounds: v.number(),
},
handler: async (ctx, args) => {
const code = generateUniqueGameCode(6);
const gameId = await ctx.db.insert("games", {
code,
creatorId: args.creatorId,
status: "waiting",
language: args.language,
players: [args.creatorId],
currentRound: 0,
maxRounds: args.maxRounds,
createdAt: Date.now(),
});
return { gameId, code };
},
});2. Player Management
2. 玩家管理
Add and remove players from game:
typescript
export const joinGame = mutation({
args: { gameCode: v.string(), playerId: v.id("users") },
handler: async (ctx, args) => {
const game = await ctx.db
.query("games")
.filter((q) => q.eq(q.field("code"), args.gameCode))
.first();
if (!game) throw new Error("Game not found");
if (game.status !== "waiting") throw new Error("Game already started");
await ctx.db.patch(game._id, {
players: [...game.players, args.playerId],
});
return game._id;
},
});添加和移除游戏中的玩家:
typescript
export const joinGame = mutation({
args: { gameCode: v.string(), playerId: v.id("users") },
handler: async (ctx, args) => {
const game = await ctx.db
.query("games")
.filter((q) => q.eq(q.field("code"), args.gameCode))
.first();
if (!game) throw new Error("Game not found");
if (game.status !== "waiting") throw new Error("Game already started");
await ctx.db.patch(game._id, {
players: [...game.players, args.playerId],
});
return game._id;
},
});3. Turn Management
3. 回合管理
Handle player turns:
typescript
export const startNextRound = mutation({
args: { gameId: v.id("games") },
handler: async (ctx, args) => {
const game = await ctx.db.get(args.gameId);
if (!game) throw new Error("Game not found");
const nextRound = game.currentRound + 1;
if (nextRound > game.maxRounds) {
// Game is finished
await ctx.db.patch(args.gameId, { status: "finished" });
return { status: "finished" };
}
// Generate questions for this round
const questions = await generateQuestionsForRound(
game.language,
game.players.length
);
await ctx.db.patch(args.gameId, {
currentRound: nextRound,
status: "in-progress",
currentQuestions: questions.map((q) => q._id),
});
return { status: "in-progress", round: nextRound };
},
});处理玩家回合:
typescript
export const startNextRound = mutation({
args: { gameId: v.id("games") },
handler: async (ctx, args) => {
const game = await ctx.db.get(args.gameId);
if (!game) throw new Error("Game not found");
const nextRound = game.currentRound + 1;
if (nextRound > game.maxRounds) {
// Game is finished
await ctx.db.patch(args.gameId, { status: "finished" });
return { status: "finished" };
}
// Generate questions for this round
const questions = await generateQuestionsForRound(
game.language,
game.players.length
);
await ctx.db.patch(args.gameId, {
currentRound: nextRound,
status: "in-progress",
currentQuestions: questions.map((q) => q._id),
});
return { status: "in-progress", round: nextRound };
},
});4. Score Calculation
4. 分数计算
Calculate points based on correctness and speed:
typescript
const calculateScore = (
isCorrect: boolean,
timeMs: number,
difficulty: "easy" | "medium" | "hard"
): number => {
if (!isCorrect) return 0;
const baseScore = { easy: 10, medium: 20, hard: 30 }[difficulty];
const timeBonus = Math.max(0, 30 - Math.floor(timeMs / 1000));
return baseScore + timeBonus;
};
export const submitAnswer = mutation({
args: {
gameId: v.id("games"),
playerId: v.id("users"),
questionId: v.id("questions"),
answer: v.string(),
timeMs: v.number(),
},
handler: async (ctx, args) => {
const question = await ctx.db.get(args.questionId);
if (!question) throw new Error("Question not found");
const isCorrect = question.correctAnswer === args.answer;
const score = calculateScore(isCorrect, args.timeMs, question.difficulty);
const answerId = await ctx.db.insert("answers", {
gameId: args.gameId,
playerId: args.playerId,
questionId: args.questionId,
answer: args.answer,
isCorrect,
timeMs: args.timeMs,
scoreEarned: score,
submittedAt: Date.now(),
});
return { answerId, isCorrect, scoreEarned: score };
},
});根据答案正确性和答题速度计算分数:
typescript
const calculateScore = (
isCorrect: boolean,
timeMs: number,
difficulty: "easy" | "medium" | "hard"
): number => {
if (!isCorrect) return 0;
const baseScore = { easy: 10, medium: 20, hard: 30 }[difficulty];
const timeBonus = Math.max(0, 30 - Math.floor(timeMs / 1000));
return baseScore + timeBonus;
};
export const submitAnswer = mutation({
args: {
gameId: v.id("games"),
playerId: v.id("users"),
questionId: v.id("questions"),
answer: v.string(),
timeMs: v.number(),
},
handler: async (ctx, args) => {
const question = await ctx.db.get(args.questionId);
if (!question) throw new Error("Question not found");
const isCorrect = question.correctAnswer === args.answer;
const score = calculateScore(isCorrect, args.timeMs, question.difficulty);
const answerId = await ctx.db.insert("answers", {
gameId: args.gameId,
playerId: args.playerId,
questionId: args.questionId,
answer: args.answer,
isCorrect,
timeMs: args.timeMs,
scoreEarned: score,
submittedAt: Date.now(),
});
return { answerId, isCorrect, scoreEarned: score };
},
});5. Game Completion
5. 游戏结束处理
Handle game ending and winner determination:
typescript
export const completeGame = mutation({
args: { gameId: v.id("games") },
handler: async (ctx, args) => {
const game = await ctx.db.get(args.gameId);
if (!game) throw new Error("Game not found");
// Get all answers for this game
const answers = await ctx.db
.query("answers")
.filter((q) => q.eq(q.field("gameId"), args.gameId))
.collect();
// Calculate final scores
const playerScores = new Map<string, number>();
answers.forEach((answer) => {
const current = playerScores.get(answer.playerId) || 0;
playerScores.set(answer.playerId, current + answer.scoreEarned);
});
const winner = Array.from(playerScores.entries()).sort(
([, a], [, b]) => b - a
)[0];
// Update user stats
await ctx.db.patch(game._id, {
status: "finished",
winnerId: winner?.[0],
finalScores: Object.fromEntries(playerScores),
});
return {
winnerId: winner?.[0],
finalScores: Object.fromEntries(playerScores),
};
},
});处理游戏结束并确定获胜者:
typescript
export const completeGame = mutation({
args: { gameId: v.id("games") },
handler: async (ctx, args) => {
const game = await ctx.db.get(args.gameId);
if (!game) throw new Error("Game not found");
// Get all answers for this game
const answers = await ctx.db
.query("answers")
.filter((q) => q.eq(q.field("gameId"), args.gameId))
.collect();
// Calculate final scores
const playerScores = new Map<string, number>();
answers.forEach((answer) => {
const current = playerScores.get(answer.playerId) || 0;
playerScores.set(answer.playerId, current + answer.scoreEarned);
});
const winner = Array.from(playerScores.entries()).sort(
([, a], [, b]) => b - a
)[0];
// Update user stats
await ctx.db.patch(game._id, {
status: "finished",
winnerId: winner?.[0],
finalScores: Object.fromEntries(playerScores),
});
return {
winnerId: winner?.[0],
finalScores: Object.fromEntries(playerScores),
};
},
});Common Edge Cases
常见边缘情况
- Simultaneous submissions: Last submission wins
- Disconnected players: Mark as inactive, don't count answers
- Time validation: Reject answers submitted after round timeout
- Language validation: Ensure selected language matches available questions
- Tie handling: Multiple players with same score
- 同时提交:以最后一次提交为准
- 玩家断开连接:标记为非活跃状态,其答案不计入统计
- 时间验证:拒绝超出回合时间限制的提交答案
- 语言验证:确保所选语言与可用题目匹配
- 平局处理:处理多名玩家分数相同的情况
Scoring Rules
计分规则
- Easy questions: 10 points base + time bonus
- Medium questions: 20 points base + time bonus
- Hard questions: 30 points base + time bonus
- Time bonus: +1 point per second remaining (max 30 seconds)
- Incorrect answers: 0 points
See Game Balance Reference for tuning parameters.
- 简单题目:10分基础分 + 时间奖励
- 中等题目:20分基础分 + 时间奖励
- 困难题目:30分基础分 + 时间奖励
- 时间奖励:每剩余1秒加1分(最多30秒)
- 错误答案:0分
有关调优参数,请参阅游戏平衡性参考文档。