@@ -108,7 +108,7 @@ const AGC_LIKE: ContestPrefix = {
108108} as const ;
109109const agcLikePrefixes = getContestPrefixes ( AGC_LIKE ) ;
110110
111- // HACK: As of early November 2024, only UTPC is included.
111+ // HACK: As of November 2024, UTPC, TTPC and TUPC are included.
112112// More university contests may be added in the future.
113113/**
114114 * Maps university contest ID prefixes to their display names.
@@ -190,7 +190,7 @@ export function getContestPrefixes(contestPrefixes: Record<string, string>) {
190190}
191191
192192/**
193- * Contest type priorities (0 = Highest, 19 = Lowest)
193+ * Contest type priorities (0 = Highest, 20 = Lowest)
194194 *
195195 * Priority assignment rationale:
196196 * - Educational contests (0-10): ABS, ABC, APG4B, etc.
@@ -240,54 +240,122 @@ export function getContestPriority(contestId: string): number {
240240 }
241241}
242242
243- export const getContestNameLabel = ( contest_id : string ) => {
244- if ( contest_id === 'APG4b' || contest_id === 'APG4bPython' ) {
245- return contest_id ;
243+ /**
244+ * Regular expression to match contest codes.
245+ *
246+ * This regex matches strings that start with one of the following prefixes:
247+ * - "abc"
248+ * - "arc"
249+ * - "agc"
250+ *
251+ * followed by exactly three digits. The matching is case-insensitive.
252+ *
253+ * Example matches:
254+ * - "abc376"
255+ * - "ARC128"
256+ * - "agc045"
257+ *
258+ * Example non-matches:
259+ * - "xyz123"
260+ * - "abc12"
261+ * - "abc1234"
262+ */
263+ const regexForAxc = / ^ ( a b c | a r c | a g c ) ( \d { 3 } ) / i;
264+
265+ /**
266+ * Regular expression to match AtCoder University contest identifiers.
267+ *
268+ * The pattern matches strings that:
269+ * - Start with either "ut", "tt", or "tu"
270+ * - Followed by "pc"
271+ * - End with exactly year (four digits)
272+ *
273+ * Example matches:
274+ * - "utpc2014"
275+ * - "ttpc2022"
276+ * - "tupc2023"
277+ */
278+ const regexForAtCoderUniversity = / ^ ( u t | t t | t u ) ( p c ) ( \d { 4 } ) / i;
279+
280+ export const getContestNameLabel = ( contestId : string ) => {
281+ // AtCoder
282+ if ( regexForAxc . exec ( contestId ) ) {
283+ return contestId . replace (
284+ regexForAxc ,
285+ ( _ , contestType , contestNumber ) => `${ contestType . toUpperCase ( ) } ${ contestNumber } ` ,
286+ ) ;
246287 }
247288
248- if ( contest_id === 'typical90' ) {
289+ if ( contestId === 'APG4b' || contestId === 'APG4bPython' ) {
290+ return contestId ;
291+ }
292+
293+ if ( contestId === 'typical90' ) {
249294 return '競プロ典型 90 問' ;
250295 }
251296
252- if ( contest_id === 'dp' ) {
297+ if ( contestId === 'dp' ) {
253298 return 'EDPC' ;
254299 }
255300
256- if ( contest_id === 'tdpc' ) {
301+ if ( contestId === 'tdpc' ) {
257302 return 'TDPC' ;
258303 }
259304
260- if ( contest_id === 'practice2' ) {
305+ if ( contestId === 'practice2' ) {
261306 return 'ACL Practice' ;
262307 }
263308
264- if ( contest_id === 'tessoku-book' ) {
309+ if ( contestId === 'tessoku-book' ) {
265310 return '競技プログラミングの鉄則' ;
266311 }
267312
268- if ( contest_id === 'math-and-algorithm' ) {
313+ if ( contestId === 'math-and-algorithm' ) {
269314 return 'アルゴリズムと数学' ;
270315 }
271316
272- if ( contest_id . startsWith ( 'chokudai_S' ) ) {
273- return contest_id . replace ( 'chokudai_S' , 'Chokudai SpeedRun ' ) ;
317+ if ( regexForAtCoderUniversity . exec ( contestId ) ) {
318+ return getAtCoderUniversityContestLabel ( contestId ) ;
274319 }
275320
276- if ( aojCoursePrefixes . has ( contest_id ) ) {
321+ if ( contestId . startsWith ( 'chokudai_S' ) ) {
322+ return contestId . replace ( 'chokudai_S' , 'Chokudai SpeedRun ' ) ;
323+ }
324+
325+ // AIZU ONLINE JUDGE
326+ if ( aojCoursePrefixes . has ( contestId ) ) {
277327 return 'AOJ Courses' ;
278328 }
279329
280- if ( contest_id . startsWith ( 'PCK' ) ) {
281- return getAojChallengeLabel ( PCK_TRANSLATIONS , contest_id ) ;
330+ if ( contestId . startsWith ( 'PCK' ) ) {
331+ return getAojChallengeLabel ( PCK_TRANSLATIONS , contestId ) ;
282332 }
283333
284- if ( contest_id . startsWith ( 'JAG' ) ) {
285- return getAojChallengeLabel ( JAG_TRANSLATIONS , contest_id ) ;
334+ if ( contestId . startsWith ( 'JAG' ) ) {
335+ return getAojChallengeLabel ( JAG_TRANSLATIONS , contestId ) ;
286336 }
287337
288- return contest_id . toUpperCase ( ) ;
338+ return contestId . toUpperCase ( ) ;
289339} ;
290340
341+ /**
342+ * Generates a formatted contest label for AtCoder University contests.
343+ *
344+ * This function takes a contest ID string and replaces parts of it using a regular expression
345+ * to generate a formatted label. The label is constructed by converting the contest type and
346+ * common part to uppercase and appending the contest year.
347+ *
348+ * @param contestId - The ID of the contest to format (ex: utpc2023).
349+ * @returns The formatted contest label (ex: UTPC 2023).
350+ */
351+ export function getAtCoderUniversityContestLabel ( contestId : string ) : string {
352+ return contestId . replace (
353+ regexForAtCoderUniversity ,
354+ ( _ , contestType , common , contestYear ) =>
355+ `${ ( contestType + common ) . toUpperCase ( ) } ${ contestYear } ` ,
356+ ) ;
357+ }
358+
291359/**
292360 * Maps PCK contest type abbreviations to their Japanese translations.
293361 *
@@ -300,26 +368,24 @@ export const getContestNameLabel = (contest_id: string) => {
300368 */
301369const PCK_TRANSLATIONS = {
302370 PCK : 'パソコン甲子園' ,
303- Prelim : '予選 ' ,
304- Final : '本選 ' ,
371+ Prelim : ' 予選 ' ,
372+ Final : ' 本選 ' ,
305373} ;
306374
307375/**
308376 * Maps JAG contest type abbreviations to their Japanese translations.
309377 *
310378 * @example
311379 * {
312- * Prelim: '模擬国内予選 ',
313- * Regional: '模擬アジア地区予選 '
380+ * Prelim: '模擬国内 ',
381+ * Regional: '模擬地区 '
314382 * }
315383 */
316384const JAG_TRANSLATIONS = {
317- Prelim : '模擬国内予選 ' ,
318- Regional : '模擬アジア地区予選 ' ,
385+ Prelim : ' 模擬国内 ' ,
386+ Regional : ' 模擬地区 ' ,
319387} ;
320388
321- const aojBaseLabel = 'AOJ - ' ;
322-
323389function getAojChallengeLabel (
324390 translations : Readonly < ContestLabelTranslations > ,
325391 contestId : string ,
@@ -330,11 +396,19 @@ function getAojChallengeLabel(
330396 label = label . replace ( abbrEnglish , japanese ) ;
331397 } ) ;
332398
333- return aojBaseLabel + label ;
399+ return '(' + label + ')' ;
334400}
335401
336402export const addContestNameToTaskIndex = ( contestId : string , taskTableIndex : string ) : string => {
337403 const contestName = getContestNameLabel ( contestId ) ;
338404
405+ if ( isAojContest ( contestId ) ) {
406+ return `AOJ ${ taskTableIndex } ${ contestName } ` ;
407+ }
408+
339409 return `${ contestName } - ${ taskTableIndex } ` ;
340410} ;
411+
412+ function isAojContest ( contestId : string ) : boolean {
413+ return contestId . startsWith ( 'PCK' ) || contestId . startsWith ( 'JAG' ) ;
414+ }
0 commit comments