Files
d1kt/server/models/SudokuRecord.ts
2026-05-15 23:21:31 +08:00

146 lines
3.2 KiB
TypeScript

import mongoose, { Document, Schema } from 'mongoose';
export type SudokuDifficulty = 'easy' | 'medium' | 'hard';
export interface ISudokuRecord extends Document {
userId: mongoose.Types.ObjectId;
username: string;
fullname: string;
difficulty: SudokuDifficulty;
gameMode: 'standard' | 'irregular';
timeSeconds: number;
won: boolean;
createdAt: Date;
updatedAt: Date;
}
export interface ISudokuLeaderboardRecord {
userId: mongoose.Types.ObjectId;
username: string;
fullname: string;
gameMode: 'standard' | 'irregular';
bestTime: number;
totalGames: number;
wonGames: number;
winRate: number;
lastPlayed: Date;
}
const sudokuRecordSchema = new Schema<ISudokuRecord>({
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
username: {
type: String,
required: true
},
fullname: {
type: String,
required: true
},
difficulty: {
type: String,
enum: ['easy', 'medium', 'hard'],
required: true
},
gameMode: {
type: String,
enum: ['standard', 'irregular'],
required: true,
default: 'standard'
},
timeSeconds: {
type: Number,
required: true,
min: 0
},
won: {
type: Boolean,
required: true
}
}, {
timestamps: true
});
sudokuRecordSchema.index({ userId: 1, difficulty: 1, gameMode: 1 });
sudokuRecordSchema.index({ difficulty: 1, won: 1, gameMode: 1, timeSeconds: 1 });
sudokuRecordSchema.statics.getLeaderboard = function(
difficulty: SudokuDifficulty,
gameMode: 'standard' | 'irregular' | 'all',
skipCount: number,
pageSize: number
): mongoose.Aggregate<ISudokuLeaderboardRecord[]> {
const match: any = {
difficulty
};
if (gameMode === 'standard') {
match.$or = [
{ gameMode: 'standard' },
{ gameMode: { $exists: false } }
];
} else if (gameMode === 'irregular') {
match.gameMode = 'irregular';
}
return this.aggregate([
{
$match: match
},
{
$group: {
_id: {
userId: '$userId',
gameMode: { $ifNull: ['$gameMode', 'standard'] }
},
username: { $first: '$username' },
fullname: { $first: '$fullname' },
gameMode: { $first: { $ifNull: ['$gameMode', 'standard'] } },
bestTime: {
$min: {
$cond: [{ $eq: ['$won', true] }, '$timeSeconds', null]
}
},
totalGames: { $sum: 1 },
wonGames: { $sum: { $cond: ['$won', 1, 0] } },
lastPlayed: { $max: '$createdAt' }
}
},
{
$match: {
bestTime: { $ne: null }
}
},
{
$addFields: {
userId: '$_id.userId',
winRate: {
$multiply: [
{ $cond: [{ $eq: ['$totalGames', 0] }, 0, { $divide: ['$wonGames', '$totalGames'] }] },
100
]
}
}
},
{ $sort: { bestTime: 1 } },
{ $skip: skipCount },
{ $limit: pageSize }
]);
};
interface SudokuRecordModel extends mongoose.Model<ISudokuRecord> {
getLeaderboard(
difficulty: SudokuDifficulty,
skipCount: number,
pageSize: number
): mongoose.Aggregate<ISudokuLeaderboardRecord[]>;
}
export const SudokuRecord = mongoose.model<ISudokuRecord, SudokuRecordModel>(
'SudokuRecord',
sudokuRecordSchema
);