feat(成就): 下架三个拿不到的成就、理顺稀有度和图标;接口多给本班几人有和「本班」卡片
- 0032:下架六边形战士(判题机只剩 3 种语言)、坚持三天/一周(按天算基本拿不到); 不撞南墙改白银、屡败屡战改黄金;百发百中换 💯、屡败屡战换 💪,不再和别的撞图标; 代码行数只算做对的、空行不算,并按新口径重算所有人;已经拿到的一个不收回 - 奖杯收藏家只数上架的成就(判题结算、补发重算、0032 存量一起改) - GET /achievements:每个成就带全站几人有、本班几人有;看自己时带「本班」(最近拿到、成就最多前五、自己第几); 隐藏成就同一串拿到一档就整串亮出来;连续几周给「现在连着几周」而不是历史最长 - 删掉只有旧成就页在用的 /achievements/summary Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
689d40b116
commit
85108df9d8
9 files changed
+4220
-213
No files matched your search
@@ -92,12 +92,12 @@ export const ACHIEVEMENT_METRICS: AchievementMetric[] = [
|
||||
{
|
||||
key: "max_code_lines",
|
||||
name: "最长代码行数",
|
||||
helpText: "提交过的最长代码有多少行",
|
||||
helpText: "做对的提交里最长的代码有多少行(空行不算,不含比赛)",
|
||||
},
|
||||
{
|
||||
key: "achievement_unlocked_count",
|
||||
name: "已解锁成就数",
|
||||
helpText: "已解锁的成就数量(不含白金档)",
|
||||
helpText: "已解锁的成就数量(不含白金档和已下架的)",
|
||||
meta: true,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -192,10 +192,12 @@ export async function updateAchievementsForSubmission(submissionId: string) {
|
||||
value.early_bird_submissions = numberMetric(value, "early_bird_submissions") + 1
|
||||
if (row.submission.result === JudgeStatus.COMPILE_ERROR)
|
||||
value.compile_error_count = numberMetric(value, "compile_error_count") + 1
|
||||
value.max_code_lines = Math.max(
|
||||
numberMetric(value, "max_code_lines"),
|
||||
row.submission.code.split(/\r?\n/).length,
|
||||
)
|
||||
// 只算做对的、空行不算:原来交什么都算,贴一大段就拿到「长篇巨制」(0032 按这个口径重算过存量)
|
||||
if (accepted)
|
||||
value.max_code_lines = Math.max(
|
||||
numberMetric(value, "max_code_lines"),
|
||||
row.submission.code.split("\n").filter((line) => line.trim() !== "").length,
|
||||
)
|
||||
await tx
|
||||
.update(schema.userStat)
|
||||
.set({ metrics: value, updateTime: new Date().toISOString() })
|
||||
@@ -213,6 +215,7 @@ export async function updateAchievementsForSubmission(submissionId: string) {
|
||||
and(
|
||||
eq(schema.userAchievement.userId, row.submission.userId),
|
||||
ne(schema.achievement.rarity, "platinum"),
|
||||
eq(schema.achievement.visible, true),
|
||||
),
|
||||
)
|
||||
metrics.achievement_unlocked_count = meta?.value ?? 0
|
||||
@@ -269,7 +272,11 @@ export async function updateAchievementsForProblemSet(userId: number) {
|
||||
.from(schema.userAchievement)
|
||||
.innerJoin(schema.achievement, eq(schema.userAchievement.achievementId, schema.achievement.id))
|
||||
.where(
|
||||
and(eq(schema.userAchievement.userId, userId), ne(schema.achievement.rarity, "platinum")),
|
||||
and(
|
||||
eq(schema.userAchievement.userId, userId),
|
||||
ne(schema.achievement.rarity, "platinum"),
|
||||
eq(schema.achievement.visible, true),
|
||||
),
|
||||
)
|
||||
metrics.achievement_unlocked_count = meta?.value ?? 0
|
||||
await db
|
||||
@@ -406,7 +413,7 @@ export function metaAchievements() {
|
||||
/**
|
||||
* 按 `user_achievement` 重算 `achievement_unlocked_count`,返回实际改动了的用户 id。
|
||||
*
|
||||
* 口径和判题结算一致:已解锁的**非白金**成就数。只 `jsonb_set` 这一个键、只写值变了的行,
|
||||
* 口径和判题结算一致:已解锁的、还上架的**非白金**成就数(下架的不算,见 0032)。只 `jsonb_set` 这一个键、只写值变了的行,
|
||||
* 不整体覆盖 `metrics` —— 整体写回会和并发判题写的其它指标互相踩。
|
||||
* 不传 `userIds` 就是全体有 `user_stat` 的用户(`recount` 存量订正用)。
|
||||
*/
|
||||
@@ -423,7 +430,7 @@ export async function refreshUnlockedCount(userIds?: number[]) {
|
||||
select ua.user_id, count(*)::int as value
|
||||
from ${schema.userAchievement} ua
|
||||
join ${schema.achievement} a on a.id = ua.achievement_id
|
||||
where a.rarity <> 'platinum'
|
||||
where a.rarity <> 'platinum' and a.visible
|
||||
group by ua.user_id
|
||||
) c on c.user_id = s.user_id
|
||||
where true ${scope}
|
||||
|
||||
@@ -99,6 +99,19 @@ function numberOf(metrics: Record<string, unknown>, key: string) {
|
||||
return typeof value === "number" ? value : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 现在连着几周有新做对。`max_ac_week_streak` 是历史最长,要拿「锲而不舍」得从现在的连续数起;
|
||||
* 只在最近一次新通过落在本周或上周时还作数,再早就已经断了。
|
||||
*/
|
||||
export function currentWeekStreak(metrics: Record<string, unknown>) {
|
||||
const monday = calendarDay(weekStart())
|
||||
const lastWeek = typeof metrics._last_ac_week === "string" ? metrics._last_ac_week : ""
|
||||
const previousMonday = dayText(dayNumber(monday) - 7)
|
||||
return lastWeek === monday || lastWeek === previousMonday
|
||||
? numberOf(metrics, "_current_ac_week_streak")
|
||||
: 0
|
||||
}
|
||||
|
||||
export async function buildProblemProgress(userId: number): Promise<ProblemProgress> {
|
||||
const [states, statRows, achievements, unlockedRows] = await Promise.all([
|
||||
problemStates(userId),
|
||||
@@ -159,13 +172,7 @@ export async function buildProblemProgress(userId: number): Promise<ProblemProgr
|
||||
for (const [day, n] of Object.entries(asRecord(metrics._ac_per_day))) {
|
||||
if (day >= monday && typeof n === "number") weekSolved += n
|
||||
}
|
||||
// 当前连续周数只在最近一次新通过落在本周或上周时还作数,再早就已经断了
|
||||
const lastWeek = typeof metrics._last_ac_week === "string" ? metrics._last_ac_week : ""
|
||||
const previousMonday = dayText(dayNumber(monday) - 7)
|
||||
const weekStreak =
|
||||
lastWeek === monday || lastWeek === previousMonday
|
||||
? numberOf(metrics, "_current_ac_week_streak")
|
||||
: 0
|
||||
const weekStreak = currentWeekStreak(metrics)
|
||||
|
||||
const unlocked = new Set(unlockedRows.map((row) => row.id))
|
||||
let next: ProblemProgress["next"] = null
|
||||
|
||||
Reference in new issue
Block a user