yingsa/server/src/services/powerCalculator.js
Ethanfly d07ebb735a fix(env): Update API URL and enhance HTML structure
- Change VITE_API_URL in .env from localhost:3000 to localhost:3001 for backend access.
- Update index.html to replace favicon with logo.png and ensure proper HTML structure.
- Add new dependencies for WangEditor in package.json and package-lock.json to support rich text editing features.
2026-02-06 19:04:16 +08:00

111 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { POWER_CALC } = require('../config/constants');
/**
* 战力值计算服务
*/
class PowerCalculator {
/**
* 计算单场比赛战力值变动
* @param {Object} winner - 胜者信息 { powerScore, level, protectedMatches }
* @param {Object} loser - 败者信息 { powerScore, level, protectedMatches }
* @param {number} matchWeight - 比赛权重
* @returns {Object} { winnerChange, loserChange }
*/
static calculate(winner, loser, matchWeight = 1.0) {
const { BASE_WIN, BASE_LOSE, UNDERDOG_THRESHOLD, UNDERDOG_RATE, MAX_CHANGE, NEWBIE_PROTECTION, NEWBIE_LOSE_RATE } = POWER_CALC;
// 计算分差
const diff = loser.powerScore - winner.powerScore;
// 基础得分
let winnerChange = BASE_WIN;
let loserChange = BASE_LOSE;
// 以下克上奖励
if (diff >= UNDERDOG_THRESHOLD) {
const bonus = Math.ceil(diff * UNDERDOG_RATE);
winnerChange += bonus;
}
// 应用比赛权重
winnerChange = Math.round(winnerChange * matchWeight);
loserChange = Math.round(loserChange * matchWeight);
// 新手保护Lv1和Lv2前5场
if (loser.level <= 2 && loser.protectedMatches < NEWBIE_PROTECTION) {
loserChange = Math.round(loserChange * NEWBIE_LOSE_RATE);
}
// 封顶限制
winnerChange = Math.min(winnerChange, MAX_CHANGE);
loserChange = Math.max(loserChange, -MAX_CHANGE);
return {
winnerChange,
loserChange
};
}
/**
* 检查挑战赛冷却期
* @param {Date} lastMatchTime - 上次比赛时间
* @returns {boolean} 是否可以挑战
*/
static canChallenge(lastMatchTime) {
if (!lastMatchTime) return true;
const cooldownDays = POWER_CALC.CHALLENGE_COOLDOWN;
const cooldownMs = cooldownDays * 24 * 60 * 60 * 1000;
const timeDiff = Date.now() - new Date(lastMatchTime).getTime();
return timeDiff >= cooldownMs;
}
/**
* 获取冷却剩余天数
* @param {Date} lastMatchTime - 上次比赛时间
* @returns {number} 剩余天数
*/
static getCooldownDays(lastMatchTime) {
if (!lastMatchTime) return 0;
const cooldownDays = POWER_CALC.CHALLENGE_COOLDOWN;
const cooldownMs = cooldownDays * 24 * 60 * 60 * 1000;
const timeDiff = Date.now() - new Date(lastMatchTime).getTime();
if (timeDiff >= cooldownMs) return 0;
return Math.ceil((cooldownMs - timeDiff) / (24 * 60 * 60 * 1000));
}
/**
* 判断升降级(排位赛结束后)
* 规则:
* - 只有冠军晋级(+1 段位,最高不超过 5 段)
* - 只有最后一名且当前段位为 4 或 5 时降级(-1 段位)
* @param {number} rank - 排名 (1-based)
* @param {number} totalPlayers - 总人数
* @returns {string} 'promote' | 'demote' | 'stay'
*/
static determinePromotion(rank, totalPlayers) {
if (rank === 1) {
return 'promote'; // 仅冠军晋级
}
if (rank === totalPlayers) {
return 'demote'; // 仅最后一名有降级资格(具体是否降,看段位)
}
return 'stay'; // 其它名次保持不变
}
/**
* 计算消费积分
* @param {number} amount - 消费金额
* @returns {number} 积分
*/
static calculateConsumePoints(amount) {
return Math.floor(amount / 10);
}
}
module.exports = PowerCalculator;