Skip to content

Commit 9a89662

Browse files
authored
Merge pull request #1243 from tsg-ut/feat/atcoder-problemset-achievements
AtCoder精進セット(EDPC/TDPC/Next DP/FPS24)の全完実績を追加
2 parents 755d58f + 05a7016 commit 9a89662

3 files changed

Lines changed: 98 additions & 12 deletions

File tree

achievements/achievements.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,60 @@ const achievements: Achievement[] = [
18241824
counter: 'atcoder-typical-solves',
18251825
value: 100,
18261826
},
1827+
{
1828+
id: 'atcoder-fps24-solves-21',
1829+
difficulty: 'hard',
1830+
title: 'FPSが得意な人',
1831+
condition: 'FPS24題の問題を21問以上ACする',
1832+
category: 'atcoder',
1833+
counter: 'atcoder-fps24-solves',
1834+
value: 21,
1835+
},
1836+
{
1837+
id: 'atcoder-fps24-solves-24',
1838+
difficulty: 'professional',
1839+
title: 'HoMaMaOvO',
1840+
condition: 'FPS24題の全問題をACする',
1841+
category: 'atcoder',
1842+
counter: 'atcoder-fps24-solves',
1843+
value: 24,
1844+
},
1845+
{
1846+
id: 'atcoder-tdpc-solves-20',
1847+
difficulty: 'hard',
1848+
title: 'DPに慣れている人',
1849+
condition: 'Typical DP Contestの全問題をACする',
1850+
category: 'atcoder',
1851+
counter: 'atcoder-tdpc-solves',
1852+
value: 20,
1853+
},
1854+
{
1855+
id: 'atcoder-edpc-solves-26',
1856+
difficulty: 'hard',
1857+
title: 'EDPCを全問解ける人',
1858+
condition: 'Educational DP Contestの全問題をACする',
1859+
category: 'atcoder',
1860+
counter: 'atcoder-edpc-solves',
1861+
value: 26,
1862+
},
1863+
{
1864+
id: 'atcoder-ndpc-solves-18',
1865+
difficulty: 'hard',
1866+
title: 'DP完全理解者',
1867+
condition: 'Next DP Contestの問題を18問以上ACする',
1868+
category: 'atcoder',
1869+
counter: 'atcoder-ndpc-solves',
1870+
value: 18,
1871+
},
1872+
{
1873+
id: 'atcoder-ndpc-solves-20',
1874+
difficulty: 'professional',
1875+
title: 'チーム参加したHoMaMaOvO(注:結託は禁止です)',
1876+
condition: 'Next DP Contestの全問題をACする',
1877+
category: 'atcoder',
1878+
counter: 'atcoder-ndpc-solves',
1879+
value: 20,
1880+
},
18271881

18281882
// pwnyaa
18291883

atcoder/index.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {getMemberIcon, getMemberName} from '../lib/slackUtils';
1515
import State from '../lib/state';
1616
// eslint-disable-next-line no-unused-vars
1717
import type {Results, Standings} from './types';
18-
import {fetchUserACsInContest} from './utils';
18+
import {fetchUserACsInContests} from './utils';
1919

2020
const log = logger.child({bot: 'atcoder'});
2121
const mutex = new Mutex();
@@ -114,6 +114,15 @@ interface ContestEntry {
114114
const SILENT_CONTEST_PREFIXES = ['awc', 'adt_'];
115115
const SILENT_START_CONTEST_PREFIXES = ['adt_'];
116116

117+
// AtCoder problem sets whose solve counts unlock completion achievements.
118+
// contestId is the AtCoder Problems contest id; counter matches an achievement in achievements.ts.
119+
const PROBLEM_SET_COUNTERS: {contestId: string, counter: string}[] = [
120+
{contestId: 'fps-24', counter: 'atcoder-fps24-solves'},
121+
{contestId: 'tdpc', counter: 'atcoder-tdpc-solves'},
122+
{contestId: 'dp', counter: 'atcoder-edpc-solves'},
123+
{contestId: 'ndpc', counter: 'atcoder-ndpc-solves'},
124+
];
125+
117126
export default async ({eventClient, webClient: slack}: SlackInterface) => {
118127
const state = await State.init<StateObj>('atcoder', {
119128
users: [],
@@ -493,15 +502,26 @@ export default async ({eventClient, webClient: slack}: SlackInterface) => {
493502
await new Promise<void>((resolve) => {
494503
setTimeout(resolve, 1000);
495504
});
496-
const absACs = await fetchUserACsInContest(user.atcoder, 'abs');
505+
// abs / typical90 are fetched alongside the completion sets but kept out of
506+
// PROBLEM_SET_COUNTERS because they don't map 1:1 to a counter
507+
// (atcoder-typical-solves counts abs + typical90 combined).
508+
const acsByContest = await fetchUserACsInContests(user.atcoder, [
509+
'abs',
510+
'typical90',
511+
...PROBLEM_SET_COUNTERS.map(({contestId}) => contestId),
512+
]).catch((error: unknown): null => {
513+
log.error(`[atcoder-daily] Failed to fetch submissions for ${user.atcoder}: ${String(error)}`);
514+
return null;
515+
});
516+
// Skip this user for today's run if their submissions could not be fetched
517+
if (acsByContest === null) {
518+
continue;
519+
}
520+
521+
const absACs = acsByContest.get('abs') ?? new Set<string>();
497522
absACs.delete('practice_1');
498523
const absSolve = absACs.size;
499-
500-
await new Promise<void>((resolve) => {
501-
setTimeout(resolve, 1000);
502-
});
503-
const typicalACs = await fetchUserACsInContest(user.atcoder, 'typical90');
504-
const typicalSolve = typicalACs.size;
524+
const typicalSolve = acsByContest.get('typical90')?.size ?? 0;
505525

506526
const previousAbsSolve = (await get(user.slack, 'atcoder-abs-solves')) || 0;
507527
if (typeof previousAbsSolve !== 'number') {
@@ -513,12 +533,17 @@ export default async ({eventClient, webClient: slack}: SlackInterface) => {
513533
}
514534
const previousTypicalSolve = previousTypicalSolves - previousAbsSolve;
515535

516-
set(user.slack, 'atcoder-abs-solves', absSolve);
517-
set(user.slack, 'atcoder-typical-solves', absSolve + typicalSolve);
536+
await set(user.slack, 'atcoder-abs-solves', absSolve);
537+
await set(user.slack, 'atcoder-typical-solves', absSolve + typicalSolve);
518538

519539
increase += Math.max(0, absSolve - previousAbsSolve);
520540
increase += Math.max(0, typicalSolve - previousTypicalSolve);
521541

542+
// Unlock completion achievements for well-known problem sets (EDPC, TDPC, etc.)
543+
for (const {contestId, counter} of PROBLEM_SET_COUNTERS) {
544+
await set(user.slack, counter, acsByContest.get(contestId)?.size ?? 0);
545+
}
546+
522547
let username = await getMemberName(user.slack);
523548
if (username.length > 12) {
524549
username = `${username.slice(0, 10)}...`;

atcoder/utils.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const getStorage = (): Promise<nodePersist.LocalStorage> => {
3232
return storagePromise;
3333
};
3434

35-
export const fetchUserACsInContest = async (userId: string, contestId: string): Promise<Set<string>> => {
35+
const syncUserSubmissions = async (userId: string): Promise<UserSubmissionCache> => {
3636
const storage = await getStorage();
3737

3838
const cacheKey = `submissions-${userId}`;
@@ -77,5 +77,12 @@ export const fetchUserACsInContest = async (userId: string, contestId: string):
7777
cached.lastFetchedSecond = maxEpochSecond;
7878
await storage.setItem(cacheKey, cached);
7979

80-
return new Set(cached.acsByContest[contestId] ?? []);
80+
return cached;
81+
};
82+
83+
// Sync a user's submissions once and return the AC sets for multiple contests,
84+
// avoiding a separate API round-trip (and throttling delay) per contest.
85+
export const fetchUserACsInContests = async (userId: string, contestIds: string[]): Promise<Map<string, Set<string>>> => {
86+
const cached = await syncUserSubmissions(userId);
87+
return new Map(contestIds.map((contestId) => [contestId, new Set(cached.acsByContest[contestId] ?? [])]));
8188
};

0 commit comments

Comments
 (0)