Skip to content

Commit 9f53e70

Browse files
authored
Merge pull request #3122 from AtCoder-NoviSteps/#3120
feat: Add and update table for ACL (#3120)
2 parents 1575875 + b2acf23 commit 9f53e70

File tree

7 files changed

+319
-16
lines changed

7 files changed

+319
-16
lines changed

docs/guides/how-to-add-contest-table-provider.md

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,48 @@
1010

1111
---
1212

13+
## 0. 実装前確認フェーズ
14+
15+
新しい Provider を実装する前に、必ず以下の事項を確認してください。
16+
17+
### 事前確認チェックリスト
18+
19+
- [ ] **ContestType の選択**
20+
- 新規追加が必要か? → `src/lib/types/contest.ts` を確認
21+
- 既存の `ContestType` で対応できないか? → 「各コンテスト種別の特有仕様」を参照
22+
- 判断基準: 複数の異なる contest_id を統一表示するなら「複合型」の可能性
23+
24+
- [ ] **データ存在確認**
25+
- `prisma/tasks.ts` に該当 `contest_id` のタスクが存在するか確認
26+
- 複合型の場合は `prisma/contest_task_pairs.ts` で共有問題を確認
27+
28+
- [ ] **実装パターン判定**
29+
- **パターン1(範囲フィルタ型)**: ABC 001~041、ARC 058~103 など → 数値範囲でフィルタ
30+
- **パターン2(単一ソース型)**: EDPC、TDPC、ACL_PRACTICE など → 単一 contest_id のみ
31+
- **パターン3(複合ソース型)**: ABS、ABC-Like など → 複数 contest_id を統一表示
32+
- 対応セクション: [実装パターン](#実装パターン)
33+
34+
- [ ] **ガイドの実装例の確認**
35+
- 判定したパターンの実装例を確認してテンプレート理解
36+
- モック設定時に必要な `classifyContest()` の戻り値を確認
37+
38+
### 記入例
39+
40+
```markdown
41+
**新規 Provider 名**: ACLBeginnerProvider
42+
43+
事前確認結果:
44+
45+
- ContestType: ABC_LIKE(既存流用)
46+
- contest_id: abl(prisma/tasks.ts に 6 つのタスク存在確認)
47+
- パターン: パターン2(単一ソース型)
48+
- テンプレート: EDPC と同一構造
49+
50+
→ 実装フェーズ開始
51+
```
52+
53+
---
54+
1355
## Test Driven Development (TDD) 設計ガイド
1456

1557
新しい Provider を実装する際は、**テストファースト** のアプローチを推奨します。
@@ -207,12 +249,16 @@ class TessokuBookSectionProvider extends TessokuBookProvider {
207249

208250
### 単一ソース型
209251

210-
| コンテスト | contest_id | セクション | フォーマット |
211-
| ------------ | ------------- | ---------- | ------------ |
212-
| EDPC | `'dp'` | 26問 | A~Z |
213-
| TDPC | `'tdpc'` | 26問 | A~Z |
214-
| FPS_24 | `'fps-24'` | 24問 | A~X |
215-
| ACL_PRACTICE | `'practice2'` | 12問 | A~L |
252+
| コンテスト | contest_id | セクション | フォーマット |
253+
| -------------- | ------------- | ---------- | ------------ |
254+
| EDPC | `'dp'` | 26問 | A~Z |
255+
| TDPC | `'tdpc'` | 26問 | A~Z |
256+
| FPS_24 | `'fps-24'` | 24問 | A~X |
257+
| ACL_PRACTICE | `'practice2'` | 12問 | A~L |
258+
| ACL_BEGINNER\* | `'abl'` | 6問 | A~F |
259+
| ACL_CONTEST1\* | `'acl1'` | 6問 | A~F |
260+
261+
\*注: ACL_PRACTICE、ACL_BEGINNER、ACL_CONTEST1 は `Acl` グループの下で 3 つのコンテストが統一管理されています。
216262

217263
### 複合ソース型
218264

@@ -419,11 +465,11 @@ describe('CustomProvider with unique config', () => {
419465
- [#2835](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2835) - ARC104OnwardsProvider
420466
- [#2837](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2837) - AGC001OnwardsProvider
421467
- [#2838](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2838) - ABC001~041 & ARC001~057
422-
- [#2840](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2840) - ABCLikeProvider
468+
- [#2840](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2840)[#3108](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3108) - ABCLikeProvider
423469
- [#2776](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2776) - TessokuBookProvider
424470
- [#2785](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2785) - MathAndAlgorithmProvider
425471
- [#2797](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2797) - FPS24Provider
426-
- [#2920](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2920) - ACLPracticeProvider
472+
- [#2920](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2920)[#3120](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3120) - ACLPracticeProvider、ACLBeginnerProvider、ACLProvider
427473

428474
### 実装ファイル
429475

@@ -433,4 +479,4 @@ describe('CustomProvider with unique config', () => {
433479

434480
---
435481

436-
**最終更新**: 2026-01-26
482+
**最終更新**: 2026-02-01

prisma/tasks.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7976,6 +7976,48 @@ export const tasks = [
79767976
name: 'Accepted...?',
79777977
title: 'A. Accepted...?',
79787978
},
7979+
{
7980+
id: 'acl1_f',
7981+
contest_id: 'acl1',
7982+
problem_index: 'F',
7983+
name: 'Center Rearranging',
7984+
title: 'F. Center Rearranging',
7985+
},
7986+
{
7987+
id: 'acl1_e',
7988+
contest_id: 'acl1',
7989+
problem_index: 'E',
7990+
name: 'Shuffle Window',
7991+
title: 'E. Shuffle Window',
7992+
},
7993+
{
7994+
id: 'acl1_d',
7995+
contest_id: 'acl1',
7996+
problem_index: 'D',
7997+
name: 'Keep Distances',
7998+
title: 'D. Keep Distances',
7999+
},
8000+
{
8001+
id: 'acl1_c',
8002+
contest_id: 'acl1',
8003+
problem_index: 'C',
8004+
name: 'Moving Pieces',
8005+
title: 'C. Moving Pieces',
8006+
},
8007+
{
8008+
id: 'acl1_b',
8009+
contest_id: 'acl1',
8010+
problem_index: 'B',
8011+
name: 'Sum is Multiple',
8012+
title: 'B. Sum is Multiple',
8013+
},
8014+
{
8015+
id: 'acl1_a',
8016+
contest_id: 'acl1',
8017+
problem_index: 'A',
8018+
name: 'Reachable Towns',
8019+
title: 'A. Reachable Towns',
8020+
},
79798021
{
79808022
id: 'tenka1_2019_f',
79818023
contest_id: 'tenka1-2019',

src/lib/utils/contest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ const ARC_LIKE: ContestPrefix = {
130130
keyence2021: 'キーエンス プログラミング コンテスト 2021',
131131
'jsc2019-qual': '第一回日本最強プログラマー学生選手権-予選-',
132132
'nikkei2019-qual': '全国統一プログラミング王決定戦予選',
133+
acl1: 'ACL Contest 1',
133134
} as const;
134135
const arcLikePrefixes = new Set(getContestPrefixes(ARC_LIKE));
135136

src/lib/utils/contest_table_provider.ts

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,70 @@ export class ACLPracticeProvider extends ContestTableProviderBase {
796796
}
797797
}
798798

799+
export class ACLBeginnerProvider extends ContestTableProviderBase {
800+
protected setFilterCondition(): (taskResult: TaskResult) => boolean {
801+
return (taskResult: TaskResult) => {
802+
if (classifyContest(taskResult.contest_id) !== this.contestType) {
803+
return false;
804+
}
805+
return taskResult.contest_id === 'abl';
806+
};
807+
}
808+
809+
getMetadata(): ContestTableMetaData {
810+
return {
811+
title: 'ACL Beginner Contest',
812+
abbreviationName: 'ABL',
813+
};
814+
}
815+
816+
getDisplayConfig(): ContestTableDisplayConfig {
817+
return {
818+
isShownHeader: false,
819+
isShownRoundLabel: false,
820+
roundLabelWidth: '',
821+
tableBodyCellsWidth: 'w-1/2 xs:w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6 px-1 py-2',
822+
isShownTaskIndex: true,
823+
};
824+
}
825+
826+
getContestRoundLabel(_contestId: string): string {
827+
return '';
828+
}
829+
}
830+
831+
export class ACLProvider extends ContestTableProviderBase {
832+
protected setFilterCondition(): (taskResult: TaskResult) => boolean {
833+
return (taskResult: TaskResult) => {
834+
if (classifyContest(taskResult.contest_id) !== this.contestType) {
835+
return false;
836+
}
837+
return taskResult.contest_id === 'acl1';
838+
};
839+
}
840+
841+
getMetadata(): ContestTableMetaData {
842+
return {
843+
title: 'ACL Contest 1',
844+
abbreviationName: 'ACL',
845+
};
846+
}
847+
848+
getDisplayConfig(): ContestTableDisplayConfig {
849+
return {
850+
isShownHeader: false,
851+
isShownRoundLabel: false,
852+
roundLabelWidth: '',
853+
tableBodyCellsWidth: 'w-1/2 xs:w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6 px-1 py-2',
854+
isShownTaskIndex: true,
855+
};
856+
}
857+
858+
getContestRoundLabel(_contestId: string): string {
859+
return '';
860+
}
861+
}
862+
799863
const regexForJoiFirstQualRound = /^(joi)(\d{4})(yo1)(a|b|c)$/i;
800864

801865
export class JOIFirstQualRoundProvider extends ContestTableProviderBase {
@@ -1209,13 +1273,18 @@ export const prepareContestProviderPresets = () => {
12091273
),
12101274

12111275
/**
1212-
* Single group for ACL Practice Contest
1276+
* Group for AtCoder Library Contests (ACL)
1277+
* Includes ACL Practice, ACL Beginner, and ACL Contest 1
12131278
*/
1214-
AclPractice: () =>
1215-
new ContestTableProviderGroup(`AtCoder Library Practice Contest`, {
1216-
buttonLabel: 'ACL Practice',
1217-
ariaLabel: 'Filter ACL Practice Contest',
1218-
}).addProvider(new ACLPracticeProvider(ContestType.ACL_PRACTICE)),
1279+
Acl: () =>
1280+
new ContestTableProviderGroup(`AtCoder Library Contests`, {
1281+
buttonLabel: 'ACL',
1282+
ariaLabel: 'Filter ACL Contests',
1283+
}).addProviders(
1284+
new ACLPracticeProvider(ContestType.ACL_PRACTICE),
1285+
new ACLBeginnerProvider(ContestType.ABC_LIKE),
1286+
new ACLProvider(ContestType.ARC_LIKE),
1287+
),
12191288

12201289
JOIFirstQualRound: () =>
12211290
new ContestTableProviderGroup(`JOI 一次予選`, {
@@ -1251,7 +1320,7 @@ export const contestTableProviderGroups = {
12511320
tessokuBook: prepareContestProviderPresets().TessokuBook(),
12521321
mathAndAlgorithm: prepareContestProviderPresets().MathAndAlgorithm(),
12531322
dps: prepareContestProviderPresets().dps(), // Dynamic Programming (DP) Contests
1254-
aclPractice: prepareContestProviderPresets().AclPractice(),
1323+
acl: prepareContestProviderPresets().Acl(),
12551324
joiFirstQualRound: prepareContestProviderPresets().JOIFirstQualRound(),
12561325
joiSecondQualAndSemiFinalRound: prepareContestProviderPresets().JOISecondQualAndSemiFinalRound(),
12571326
};

0 commit comments

Comments
 (0)