增加天梯排行榜
This commit is contained in:
55
ladder/ladder.controller.js
Normal file
55
ladder/ladder.controller.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const ladderModel = require('./ladder.model');
|
||||
const ladderService = require('./ladder.service');
|
||||
const { UserModel } = require('../users/users.model');
|
||||
|
||||
// Get leaderboard
|
||||
exports.getLeaderboard = async (req, res) => {
|
||||
try {
|
||||
const leaderboard = await ladderModel.getLeaderboard();
|
||||
res.status(200).send(leaderboard);
|
||||
} catch (error) {
|
||||
console.error('Error getting leaderboard:', error);
|
||||
res.status(500).send({ error: 'Failed to get leaderboard' });
|
||||
}
|
||||
};
|
||||
|
||||
// Get player's position in leaderboard
|
||||
exports.getPlayerPosition = async (req, res) => {
|
||||
try {
|
||||
const { playerId } = req.params;
|
||||
const position = await ladderModel.getPlayerPosition(playerId);
|
||||
res.status(200).send({ position });
|
||||
} catch (error) {
|
||||
console.error('Error getting player position:', error);
|
||||
res.status(500).send({ error: 'Failed to get player position' });
|
||||
}
|
||||
};
|
||||
|
||||
// Get player's rank info
|
||||
exports.getPlayerRankInfo = async (req, res) => {
|
||||
try {
|
||||
const { playerId } = req.params;
|
||||
const player = await UserModel.getById(playerId);
|
||||
|
||||
if (!player) {
|
||||
return res.status(404).send({ error: 'Player not found' });
|
||||
}
|
||||
|
||||
const rankInfo = ladderService.getPlayerRankInfo(player);
|
||||
res.status(200).send(rankInfo);
|
||||
} catch (error) {
|
||||
console.error('Error getting player rank info:', error);
|
||||
res.status(500).send({ error: 'Failed to get player rank info' });
|
||||
}
|
||||
};
|
||||
|
||||
// Get all rank configurations
|
||||
exports.getRankConfigurations = async (req, res) => {
|
||||
try {
|
||||
const configs = ladderService.getAllRankConfigs();
|
||||
res.status(200).send(configs);
|
||||
} catch (error) {
|
||||
console.error('Error getting rank configurations:', error);
|
||||
res.status(500).send({ error: 'Failed to get rank configurations' });
|
||||
}
|
||||
};
|
||||
119
ladder/ladder.model.js
Normal file
119
ladder/ladder.model.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const ladderService = require('./ladder.service');
|
||||
|
||||
const leaderboardSchema = new Schema({
|
||||
playerId: { type: String, index: true, required: true },
|
||||
username: { type: String, required: true },
|
||||
avatar: { type: String, default: "" },
|
||||
rankId: { type: Number, default: 1 },
|
||||
rankScore: { type: Number, default: 0 },
|
||||
stars: { type: Number, default: 0 },
|
||||
totalWins: { type: Number, default: 0 },
|
||||
position: { type: Number, required: true },
|
||||
lastUpdated: { type: Date, default: Date.now }
|
||||
});
|
||||
|
||||
const Leaderboard = mongoose.model('Leaderboard', leaderboardSchema);
|
||||
|
||||
// Leaderboard functions
|
||||
exports.generateLeaderboard = async () => {
|
||||
try {
|
||||
// Clear current leaderboard
|
||||
await Leaderboard.deleteMany({});
|
||||
|
||||
// Get all users
|
||||
const User = mongoose.model('Users');
|
||||
const users = await User.find({});
|
||||
|
||||
// Sort users based on ranking criteria:
|
||||
// 1. Rank level (higher is better)
|
||||
// 2. Rank score (for王者分数 mechanism)
|
||||
// 3. Stars
|
||||
// 4. Total wins
|
||||
const sortedUsers = users.sort((a, b) => {
|
||||
const configA = ladderService.getRankConfig(a.rankId);
|
||||
const configB = ladderService.getRankConfig(b.rankId);
|
||||
|
||||
// Compare rank levels
|
||||
if (configA.Level !== configB.Level) {
|
||||
return configB.Level - configA.Level;
|
||||
}
|
||||
|
||||
// For players with rank score mechanism
|
||||
if (configA.RankScore === 1 && configB.RankScore === 1) {
|
||||
if (a.rankScore !== b.rankScore) {
|
||||
return b.rankScore - a.rankScore;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare stars
|
||||
if (a.stars !== b.stars) {
|
||||
return b.stars - a.stars;
|
||||
}
|
||||
|
||||
// Compare total wins
|
||||
return b.totalWins - a.totalWins;
|
||||
});
|
||||
|
||||
// Take top 100 players
|
||||
const topPlayers = sortedUsers.slice(0, 100);
|
||||
|
||||
// Create leaderboard entries
|
||||
const leaderboardEntries = [];
|
||||
for (let i = 0; i < topPlayers.length; i++) {
|
||||
const player = topPlayers[i];
|
||||
const entry = new Leaderboard({
|
||||
playerId: player._id,
|
||||
username: player.username,
|
||||
avatar: player.avatar,
|
||||
rankId: player.rankId,
|
||||
rankScore: player.rankScore,
|
||||
stars: player.stars,
|
||||
totalWins: player.totalWins,
|
||||
position: i + 1
|
||||
});
|
||||
leaderboardEntries.push(entry);
|
||||
}
|
||||
|
||||
// Save all entries
|
||||
if (leaderboardEntries.length > 0) {
|
||||
await Leaderboard.insertMany(leaderboardEntries);
|
||||
}
|
||||
|
||||
return leaderboardEntries;
|
||||
} catch (error) {
|
||||
console.error('Error generating leaderboard:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.getLeaderboard = async () => {
|
||||
try {
|
||||
const leaderboard = await Leaderboard.find({}).sort({ position: 1 });
|
||||
return leaderboard;
|
||||
} catch (error) {
|
||||
console.error('Error getting leaderboard:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.getPlayerPosition = async (playerId) => {
|
||||
try {
|
||||
const entry = await Leaderboard.findOne({ playerId: playerId });
|
||||
return entry ? entry.position : null;
|
||||
} catch (error) {
|
||||
console.error('Error getting player position:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize the ladder for a new player
|
||||
exports.initializePlayerLadder = (player) => {
|
||||
player.rankId = 1;
|
||||
player.stars = 0;
|
||||
player.rankScore = 0;
|
||||
player.winStreak = 0;
|
||||
player.totalWins = 0;
|
||||
return player;
|
||||
};
|
||||
15
ladder/ladder.routes.js
Normal file
15
ladder/ladder.routes.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const ladderController = require('./ladder.controller');
|
||||
|
||||
exports.route = (app) => {
|
||||
// Get leaderboard
|
||||
app.get('/ladder/leaderboard', ladderController.getLeaderboard);
|
||||
|
||||
// Get player position in leaderboard
|
||||
app.get('/ladder/position/:playerId', ladderController.getPlayerPosition);
|
||||
|
||||
// Get player rank info
|
||||
app.get('/ladder/rank/:playerId', ladderController.getPlayerRankInfo);
|
||||
|
||||
// Get all rank configurations
|
||||
app.get('/ladder/config', ladderController.getRankConfigurations);
|
||||
};
|
||||
144
ladder/ladder.service.js
Normal file
144
ladder/ladder.service.js
Normal file
@@ -0,0 +1,144 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Load ladder configuration
|
||||
const ladderConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'ladder-config.json')));
|
||||
|
||||
class LadderService {
|
||||
constructor() {
|
||||
this.rankConfigs = new Map();
|
||||
this.initConfig();
|
||||
}
|
||||
|
||||
initConfig() {
|
||||
ladderConfig.forEach(config => {
|
||||
this.rankConfigs.set(config.Id, config);
|
||||
});
|
||||
}
|
||||
|
||||
// Get rank configuration by ID
|
||||
getRankConfig(rankId) {
|
||||
return this.rankConfigs.get(rankId);
|
||||
}
|
||||
|
||||
// Get all rank configurations
|
||||
getAllRankConfigs() {
|
||||
return Array.from(this.rankConfigs.values());
|
||||
}
|
||||
|
||||
// Handle player win
|
||||
async handleWin(player, opponent) {
|
||||
const config = this.getRankConfig(player.rankId);
|
||||
|
||||
if (config.RankScore === 1) {
|
||||
// 王者分数 mechanism
|
||||
this.updateRankScore(player, opponent, true);
|
||||
} else {
|
||||
// Star-based mechanism
|
||||
this.updateStars(player, config, true);
|
||||
}
|
||||
|
||||
player.totalWins++;
|
||||
player.winStreak++;
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
// Handle player loss
|
||||
async handleLoss(player, opponent) {
|
||||
const config = this.getRankConfig(player.rankId);
|
||||
|
||||
if (config.RankScore === 1) {
|
||||
// 王者分数 mechanism
|
||||
this.updateRankScore(player, opponent, false);
|
||||
} else {
|
||||
// Star-based mechanism
|
||||
this.updateStars(player, config, false);
|
||||
}
|
||||
|
||||
player.winStreak = 0;
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
updateStars(player, config, isWin) {
|
||||
if (isWin) {
|
||||
let starsToAdd = config.WinGetStar;
|
||||
// Add extra stars for win streak
|
||||
if (player.winStreak >= 3) {
|
||||
starsToAdd += config.ExtraGetStar;
|
||||
}
|
||||
player.stars += starsToAdd;
|
||||
|
||||
// Level up check
|
||||
if (player.stars > config.MaxStar) {
|
||||
this.levelUp(player);
|
||||
}
|
||||
} else {
|
||||
if (config.LoseLostStar === 1) {
|
||||
player.stars = Math.max(0, player.stars - 1);
|
||||
|
||||
// Level down check
|
||||
if (player.stars === 0) {
|
||||
this.levelDown(player, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateRankScore(player, opponent, isWin) {
|
||||
if (isWin) {
|
||||
let scoreToAdd = 20;
|
||||
// If opponent has a higher rank score, calculate bonus
|
||||
if (opponent.rankScore > player.rankScore) {
|
||||
const ratio = Math.min(opponent.rankScore / player.rankScore, 3);
|
||||
scoreToAdd = Math.round(ratio * 20);
|
||||
}
|
||||
player.rankScore += scoreToAdd;
|
||||
} else {
|
||||
// Deduct points on loss
|
||||
if (player.rankScore > 0) {
|
||||
player.rankScore = Math.max(0, player.rankScore - 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
levelUp(player) {
|
||||
const nextConfig = this.getRankConfig(player.rankId + 1);
|
||||
if (nextConfig) {
|
||||
player.rankId++;
|
||||
player.stars = nextConfig.BeginStar;
|
||||
}
|
||||
}
|
||||
|
||||
levelDown(player, currentConfig) {
|
||||
if (currentConfig.LoseRankDown === 1 && player.rankId > 1) {
|
||||
player.rankId--;
|
||||
const prevConfig = this.getRankConfig(player.rankId);
|
||||
player.stars = prevConfig.RankDownStar;
|
||||
}
|
||||
}
|
||||
|
||||
// Get player's rank display info
|
||||
getPlayerRankInfo(player) {
|
||||
const config = this.getRankConfig(player.rankId);
|
||||
if (config.RankScore === 1) {
|
||||
return {
|
||||
rankName: config.RankName,
|
||||
level: config.Level,
|
||||
score: player.rankScore,
|
||||
isRankScore: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
rankName: config.RankName,
|
||||
level: config.Level,
|
||||
stars: player.stars,
|
||||
maxStars: config.MaxStar,
|
||||
isRankScore: false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new LadderService();
|
||||
Reference in New Issue
Block a user