0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub Online Judgement

Posted at

t5ugu です。
MMA Advent Calender 2025 21日目の記事です。

昨日は sadatch さんの「一般ヲタクによるコミケ巡回の流儀」でした。
コミケには一度は行ってみたいですね。

今日の記事は、予定とは変わって、GitHub Actions 体験記です。

TL;DR

GitHub Actions を使った、アルゴリズムコンテスト向けのジャッジシステムを考えてみます。
成果はこちら。

はじめに

プログラミングコンテストは、ジャッジ環境の基盤の上で成り立っています。
自作を通して、既存のものが優秀なことを実感してみましょう。

自分でホストする方法は Docker とかがあります。
今回は、CI/CD でおなじみの GitHub Actions を使って作りました。

GitHub も、競技プログラミングも初心者ですので、試行錯誤の過程を生暖かい目で見ていただけると幸いです。

コンテストに関わるすべての要素をインターネット上に配置する必要があります。
テクニックによって問題やケースが漏洩すれば、競技性が著しく損なわれるでしょう。

作り方

概観

まずは、いくつかのドキュメントや記事を読むことで、GitHub Actions は工夫次第で何でもできそうだ、という気持ちになります。

競技プログラミングのためのジャッジ環境の基本要件として、

  • 問題・解説・実行結果など、人間が読む部分
  • テストケースと期待する出力の集まり
  • プログラムを受け取り、実行し、結果を得る

の3つのセクションがあると分析しました。

このうち、1 つめの人間が読む部分はフロントエンドに託せばよく、凝るのは非本質的です。
また、2 つめは、できれば制約から自動でテストケースを作る機構もあるとよいですが、最悪 Writer が頑張れば用意できます。

このため、3 つめの「プログラムを受け取り、実行し、結果を得る」という部分を GitHub Actions(と ChatGPT)で作成することにしました。

まずはハードに

問題は、問題セットに依らないで、一意に決定できると仮定します。
つまり、各問題は PRB001_A などと命名されているものとします。

problems には、問題データ(人が読む文章や、テストケースとその模範解答)を配置します。
submissions には、コンテスタントが解いた答案(現状 Python のみ)を配置します。

開発初期は on: workflow_dispatch に引数を渡すことでのみ発火するようにしていました。
答案を 1 つだけ受け取って、ジャッジする流れです。

そして柔軟に

problems/**/ には specification.json という説明書きを配置しました。
問題の ID、CPU の時間制限、テストケース名が記されています。

problems/PRB001_A/specification.json
{
    "id": "PRB001_A",
    "cpu_limit_seconds": 2,
    "cases": [
        "sample/hand_00",
        "sample/hand_01"
    ]
}

時間計測やその他処理は、最初は GitHub Actions を Ubuntsu で走らせていることを理由に Bash を用いていました(judge.sh)。
途中で、Python の実行環境をセットアップしているついでに、メイン処理も Python で動かすように変更しました(judge_runner.py)。

ジャッジ基準

TLE (Time Limit Exceeded) は、CPU 時間を 2 秒使うか、現実世界で 2+5 = 7 秒経過するか、で判断しています。
sleep 系の関数を使うと、CPU 時間を使わずに処理が進められると思ったため、二重に測定しています。

MLE (Memory Limit Exceeded) は、メモリを制限する方法が思いつかなかったので無いです。

RE (Rumtime Error) は、子となる Python プロセスが異常終了のエラーコードを吐くことを基準にしています。

WA (Wrong Answer) は、テストケースに紐づいた模範出力との完全一致で確かめています。
このため、まだ小数は扱えないです。

できたもの

ディレクトリ構成

root/
|- .github/workflows/
|  |- judge.yml
|
|- judgesys/
|  |- judge_runner.py
|
|- problems/
|  |- PRB001_A/
|  |  |- README.md
|  |  |- specification.json
|  |  |- cases/sample
|  |     |- hand_00.txt
|  |     |- hand_00_out.txt
|  |     |- ...
|  |- ...
|
|- submissions/
   |- PRB001_A/
   |  |- answer.py
   |  |- ...
   |- ...

Judge.yml

ほとんどコードエージェントに書いてもらったので、詳しい話はできませんが、流れだけ。

  1. Pull Request を提出箱とし、オープン時・ファイル更新時・再オープン時に、submissions/**/ に Python ファイルが追加されているなら Action を発火する
  2. 答案ファイルの path を収集する
  3. 動的に Job を作成できる Matrix を使い、それぞれの答案ファイルに対して、メイン処理 judge_runner.py を実行する
  4. 答案ファイルのパスから問題 ID を取得し、specifcation.json を読む
  5. 各テストケースに対し、実行して結果を得る
  6. ::error:: を用いて出力し、通っていないケースを公開する

具体的な実行結果は以下にあります。

さいごに

なんとまぁ、AI と共に粗雑ながらジャッジ環境を作れてしまいました。
もっと見やすく、もっと扱いやすく、という展望はありますが、今回はこの辺りで。

MMA Contest 021 もぜひどうぞ!
また、MMA Advent Calender 2025 の他の記事もごひいきに。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?