Skip to content

Commit 674ab7f

Browse files
authored
Merge pull request #2322 from AtCoder-NoviSteps/#2321
✨ Add JOI first qual round to contest table (#2321)
2 parents 2c4f2a9 + 50f3957 commit 674ab7f

File tree

5 files changed

+388
-3
lines changed

5 files changed

+388
-3
lines changed

prisma/tasks.ts

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4820,6 +4820,56 @@ export const tasks = [
48204820
title: 'A01. The First Problem',
48214821
grade: 'Q10',
48224822
},
4823+
{
4824+
id: 'joi2025_yo1c_d',
4825+
contest_id: 'joi2025yo1c',
4826+
problem_index: 'D',
4827+
name: '周期文字列 (Cycle String)',
4828+
title: 'D. 周期文字列 (Cycle String)',
4829+
},
4830+
{
4831+
id: 'joi2025_yo1c_c',
4832+
contest_id: 'joi2025yo1c',
4833+
problem_index: 'C',
4834+
name: 'いずれか片方 (Either, but Not Both)',
4835+
title: 'C. いずれか片方 (Either, but Not Both)',
4836+
},
4837+
{
4838+
id: 'joi2025_yo1c_b',
4839+
contest_id: 'joi2025yo1c',
4840+
problem_index: 'B',
4841+
name: 'ブラックジャック (Blackjack)',
4842+
title: 'B. ブラックジャック (Blackjack)',
4843+
},
4844+
{
4845+
id: 'joi2025_yo1c_a',
4846+
contest_id: 'joi2025yo1c',
4847+
problem_index: 'A',
4848+
name: '所持金 (Money On Me)',
4849+
title: 'A. 所持金 (Money On Me)',
4850+
},
4851+
{
4852+
id: 'joi2025_yo1b_d',
4853+
contest_id: 'joi2025yo1b',
4854+
problem_index: 'D',
4855+
name: '三角足し算 (Triangle Addition)',
4856+
title: 'D. 三角足し算 (Triangle Addition)',
4857+
},
4858+
{
4859+
id: 'joi2025_yo1b_c',
4860+
contest_id: 'joi2025yo1b',
4861+
problem_index: 'C',
4862+
name: 'じゃんけん (Rock-Scissors-Paper)',
4863+
title: 'C. じゃんけん (Rock-Scissors-Paper)',
4864+
},
4865+
{
4866+
id: 'joi2025_yo1b_b',
4867+
contest_id: 'joi2025yo1b',
4868+
problem_index: 'B',
4869+
name: '鉄道旅行 3 (Railway Trip 3)',
4870+
title: 'B. 鉄道旅行 3 (Railway Trip 3)',
4871+
grade: 'Q8',
4872+
},
48234873
{
48244874
id: 'joi2025_yo1b_a',
48254875
contest_id: 'joi2025yo1b',
@@ -4828,6 +4878,22 @@ export const tasks = [
48284878
title: 'A. 徒競走 (Footrace)',
48294879
grade: 'Q9',
48304880
},
4881+
{
4882+
id: 'joi2025_yo1a_d',
4883+
contest_id: 'joi2025yo1a',
4884+
problem_index: 'D',
4885+
name: 'どら焼き (Dorayaki)',
4886+
title: 'D. どら焼き (Dorayaki)',
4887+
grade: 'Q7',
4888+
},
4889+
{
4890+
id: 'joi2025_yo1a_c',
4891+
contest_id: 'joi2025yo1a',
4892+
problem_index: 'C',
4893+
name: 'OIJ (OIJ)',
4894+
title: 'C. OIJ (OIJ)',
4895+
grade: 'Q7',
4896+
},
48314897
{
48324898
id: 'joi2025_yo1a_b',
48334899
contest_id: 'joi2025yo1a',
@@ -4916,6 +4982,38 @@ export const tasks = [
49164982
title: 'A. 立方体 (Cube)',
49174983
grade: 'Q10',
49184984
},
4985+
{
4986+
id: 'joi2022_yo1a_d',
4987+
contest_id: 'joi2022yo1a',
4988+
problem_index: 'D',
4989+
name: '箱と鍵 (Boxes and Keys)',
4990+
title: 'D. 箱と鍵 (Boxes and Keys)',
4991+
grade: 'Q6',
4992+
},
4993+
{
4994+
id: 'joi2022_yo1a_c',
4995+
contest_id: 'joi2022yo1a',
4996+
problem_index: 'C',
4997+
name: '複雑な文字列 (Complex String)',
4998+
title: 'C. 複雑な文字列 (Complex String)',
4999+
grade: 'Q7',
5000+
},
5001+
{
5002+
id: 'joi2022_yo1a_b',
5003+
contest_id: 'joi2022yo1a',
5004+
problem_index: 'B',
5005+
name: '移動 (Moving)',
5006+
title: 'B. 移動 (Moving)',
5007+
grade: 'Q8',
5008+
},
5009+
{
5010+
id: 'joi2022_yo1a_a',
5011+
contest_id: 'joi2022yo1a',
5012+
problem_index: 'A',
5013+
name: '余り (Remainder)',
5014+
title: 'A. 余り (Remainder)',
5015+
grade: 'Q9',
5016+
},
49195017
{
49205018
id: 'joi2021_yo1b_a',
49215019
contest_id: 'joi2021yo1b',
@@ -4932,6 +5030,102 @@ export const tasks = [
49325030
title: 'A. 金平糖 (Konpeito)',
49335031
grade: 'Q8',
49345032
},
5033+
{
5034+
id: 'joi2021_yo1a_c',
5035+
contest_id: 'joi2021yo1a',
5036+
problem_index: 'C',
5037+
name: '共通要素 (Common Elements)',
5038+
title: 'C. 共通要素 (Common Elements)',
5039+
grade: 'Q6',
5040+
},
5041+
{
5042+
id: 'joi2021_yo1a_b',
5043+
contest_id: 'joi2021yo1a',
5044+
problem_index: 'B',
5045+
name: 'JOI ソート (JOI Sort)',
5046+
title: 'B. JOI ソート (JOI Sort)',
5047+
grade: 'Q7',
5048+
},
5049+
{
5050+
id: 'joi2021_yo1a_a',
5051+
contest_id: 'joi2021yo1a',
5052+
problem_index: 'A',
5053+
name: '2 番目に大きい整数 (The Second Largest Integer)',
5054+
title: 'A. 2 番目に大きい整数 (The Second Largest Integer)',
5055+
grade: 'Q8',
5056+
},
5057+
{
5058+
id: 'joi2020_yo1c_c',
5059+
contest_id: 'joi2020yo1c',
5060+
problem_index: 'C',
5061+
name: '最長昇順連続部分列 (Longest Ascending Contiguous Subsequence)',
5062+
title: 'C. 最長昇順連続部分列 (Longest Ascending Contiguous Subsequence)',
5063+
grade: 'Q6',
5064+
},
5065+
{
5066+
id: 'joi2020_yo1c_b',
5067+
contest_id: 'joi2020yo1c',
5068+
problem_index: 'B',
5069+
name: 'キャピタリゼーション (Capitalization)',
5070+
title: 'B. キャピタリゼーション (Capitalization)',
5071+
grade: 'Q7',
5072+
},
5073+
{
5074+
id: 'joi2020_yo1c_a',
5075+
contest_id: 'joi2020yo1c',
5076+
problem_index: 'A',
5077+
name: 'X に最も近い値 (The Nearest Value)',
5078+
title: 'A. X に最も近い値 (The Nearest Value)',
5079+
grade: 'Q7',
5080+
},
5081+
{
5082+
id: 'joi2020_yo1b_c',
5083+
contest_id: 'joi2020yo1b',
5084+
problem_index: 'C',
5085+
name: '最頻値 (Mode)',
5086+
title: 'C. 最頻値 (Mode)',
5087+
grade: 'Q6',
5088+
},
5089+
{
5090+
id: 'joi2020_yo1b_b',
5091+
contest_id: 'joi2020yo1b',
5092+
problem_index: 'B',
5093+
name: '文字列の反転 (Inversion of a String)',
5094+
title: 'B. 文字列の反転 (Inversion of a String)',
5095+
grade: 'Q7',
5096+
},
5097+
{
5098+
id: 'joi2020_yo1b_a',
5099+
contest_id: 'joi2020yo1b',
5100+
problem_index: 'A',
5101+
name: '試験 (Exam)',
5102+
title: 'A. 試験 (Exam)',
5103+
grade: 'Q8',
5104+
},
5105+
{
5106+
id: 'joi2020_yo1a_c',
5107+
contest_id: 'joi2020yo1a',
5108+
problem_index: 'C',
5109+
name: 'マージ (Merge)',
5110+
title: 'C. マージ (Merge)',
5111+
grade: 'Q5',
5112+
},
5113+
{
5114+
id: 'joi2020_yo1a_b',
5115+
contest_id: 'joi2020yo1a',
5116+
problem_index: 'B',
5117+
name: '母音を数える (Counting Vowels)',
5118+
title: 'B. 母音を数える (Counting Vowels)',
5119+
grade: 'Q7',
5120+
},
5121+
{
5122+
id: 'joi2020_yo1a_a',
5123+
contest_id: 'joi2020yo1a',
5124+
problem_index: 'A',
5125+
name: '3 つの整数 (Three Integers)',
5126+
title: 'A. 3 つの整数 (Three Integers)',
5127+
grade: 'Q9',
5128+
},
49355129
{
49365130
id: 'joi2016yo_a',
49375131
contest_id: 'joi2016yo',

src/lib/components/TaskTables/TaskTable.svelte

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@
106106
}
107107
108108
function getBodyCellClasses(taskResult: TaskResult, totalColumns: number): string {
109-
const baseClasses = 'w-1/2 xs:w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6 px-1 py-1';
109+
const baseClasses =
110+
totalColumns >= 5
111+
? 'w-1/2 xs:w-1/3 sm:w-1/4 md:w-1/5 lg:w-1/6 px-1 py-1'
112+
: 'w-1/2 xs:w-1/3 sm:w-1/4 px-1 py-1';
110113
const additionalClasses = totalColumns > 8 ? '2xl:w-1/7 py-2' : '';
111114
const backgroundColor = getBackgroundColor(taskResult);
112115
@@ -199,7 +202,12 @@
199202
<div class="w-full sticky top-0 z-20 border-b border-gray-200 dark:border-gray-100">
200203
<Table id="task-table" class="text-md table-fixed w-full" aria-label="Task table">
201204
<TableHead class="text-sm border-gray-200 dark:border-gray-100">
202-
<TableHeadCell class="w-full xl:w-16 px-2 text-center" scope="col">Round</TableHeadCell>
205+
<TableHeadCell
206+
class="w-full {contestTable.displayConfig.roundLabelWidth} px-2 text-center"
207+
scope="col"
208+
>
209+
Round
210+
</TableHeadCell>
203211

204212
{#if contestTable.headerIds}
205213
{#each contestTable.headerIds as taskTableHeaderId (taskTableHeaderId)}
@@ -224,7 +232,8 @@
224232
<TableBodyRow class={getBodyRowClasses(totalColumns)}>
225233
{#if contestTable.displayConfig.isShownRoundLabel}
226234
<TableBodyCell
227-
class="w-full xl:w-16 truncate px-2 py-2 text-center bg-gray-50 dark:bg-gray-800"
235+
class="w-full {contestTable.displayConfig
236+
.roundLabelWidth} truncate px-2 py-2 text-center bg-gray-50 dark:bg-gray-800"
228237
>
229238
{getContestRoundLabel(provider, contestId)}
230239
</TableBodyCell>

src/lib/types/contest_table_provider.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,12 @@ export type ContestTablesMetaData = {
125125
* @interface ContestTableDisplayConfig
126126
* @property {boolean} isShownHeader - Whether to display the table header
127127
* @property {boolean} isShownRoundLabel - Whether to display round labels in the contest table
128+
* @property {string} roundLabelWidth - tailwind CSS width for the round label column, e.g., "w-16" or "w-20"
128129
* @property {boolean} isShownTaskIndex - Whether to display task index in the contest table cells
129130
*/
130131
export interface ContestTableDisplayConfig {
131132
isShownHeader: boolean;
132133
isShownRoundLabel: boolean;
134+
roundLabelWidth: string;
133135
isShownTaskIndex: boolean;
134136
}

src/lib/utils/contest_table_provider.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export abstract class ContestTableProviderBase implements ContestTableProvider {
9898
return {
9999
isShownHeader: true,
100100
isShownRoundLabel: true,
101+
roundLabelWidth: 'xl:w-16', // Default width for task index column
101102
isShownTaskIndex: false,
102103
};
103104
}
@@ -230,6 +231,7 @@ export class EDPCProvider extends ContestTableProviderBase {
230231
return {
231232
isShownHeader: false,
232233
isShownRoundLabel: false,
234+
roundLabelWidth: '', // No specific width for task index in EDPC
233235
isShownTaskIndex: true,
234236
};
235237
}
@@ -261,6 +263,7 @@ export class TDPCProvider extends ContestTableProviderBase {
261263
return {
262264
isShownHeader: false,
263265
isShownRoundLabel: false,
266+
roundLabelWidth: '', // No specific width for task index in TDPC
264267
isShownTaskIndex: true,
265268
};
266269
}
@@ -270,6 +273,41 @@ export class TDPCProvider extends ContestTableProviderBase {
270273
}
271274
}
272275

276+
const regexForJoiFirstQualRound = /^(joi)(\d{4})(yo1)(a|b|c)$/i;
277+
278+
export class JOIFirstQualRoundProvider extends ContestTableProviderBase {
279+
protected setFilterCondition(): (taskResult: TaskResult) => boolean {
280+
return (taskResult: TaskResult) => {
281+
if (classifyContest(taskResult.contest_id) !== this.contestType) {
282+
return false;
283+
}
284+
285+
return regexForJoiFirstQualRound.test(taskResult.contest_id);
286+
};
287+
}
288+
289+
getMetadata(): ContestTableMetaData {
290+
return {
291+
title: 'JOI 一次予選',
292+
abbreviationName: 'joiFirstQualRound',
293+
};
294+
}
295+
296+
getDisplayConfig(): ContestTableDisplayConfig {
297+
return {
298+
isShownHeader: true,
299+
isShownRoundLabel: true,
300+
isShownTaskIndex: false,
301+
roundLabelWidth: 'xl:w-28',
302+
};
303+
}
304+
305+
getContestRoundLabel(contestId: string): string {
306+
const contestNameLabel = getContestNameLabel(contestId);
307+
return contestNameLabel.replace('JOI 一次予選 ', '');
308+
}
309+
}
310+
273311
/**
274312
* A class that manages individual provider groups
275313
* Manages multiple ContestTableProviders as a single group,
@@ -415,6 +453,12 @@ export const prepareContestProviderPresets = () => {
415453
{ contestType: ContestType.EDPC, provider: new EDPCProvider(ContestType.EDPC) },
416454
{ contestType: ContestType.TDPC, provider: new TDPCProvider(ContestType.TDPC) },
417455
),
456+
457+
JOIFirstQualRound: () =>
458+
new ContestTableProviderGroup(`JOI 一次予選`, {
459+
buttonLabel: 'JOI 一次予選',
460+
ariaLabel: 'Filter JOI First Qualifying Round',
461+
}).addProvider(ContestType.JOI, new JOIFirstQualRoundProvider(ContestType.JOI)),
418462
};
419463
};
420464

@@ -423,6 +467,7 @@ export const contestTableProviderGroups = {
423467
abc319Onwards: prepareContestProviderPresets().ABC319Onwards(),
424468
fromAbc212ToAbc318: prepareContestProviderPresets().ABC212ToABC318(),
425469
dps: prepareContestProviderPresets().dps(), // Dynamic Programming (DP) Contests
470+
joiFirstQualRound: prepareContestProviderPresets().JOIFirstQualRound(),
426471
};
427472

428473
export type ContestTableProviderGroups = keyof typeof contestTableProviderGroups;

0 commit comments

Comments
 (0)