DMOJ を使ってオンラインジャッジシステムを構築する

DMOJ という OSS のオンラインジャッジシステムの環境を整えたので使い方をメモしておく。

環境はコンテナ化し、image も Docker Hub に上げたので MySQL や Redis のイメージが消されない限りは動くと思う。(動作確認は Linux 環境でのみ行っている)

github.com

元々のコードだとメールアドレスの認証が必須になっていたが、もっと雑にユーザー登録をできるようにしたかったのでメールアドレスの認証は飛ばすように変更している。(AWS SES などとつなげるのが面倒だった)

オリジナルとの変更点はこの commit
my custom · goropikari/online-judge@02a4a4a · GitHub

基本構成

GitHub で DMOJ Organization を見ると DMOJ/online-judge というのがあるがこれだけではオンラインジャッジシステムとして使うことはできない。これはあくまでフロントエンドであって実際に提出されたプログラムの正誤判定をするジャッジサーバーは DMOJ/judge-server である。よってこれら2つをつなげることでオンラインジャッジシステムとして使うことができる。 ジャッジサーバーは複数登録することができるが、提出されるプログラム数に応じてオートスケーリングするとかはなくて手動で登録するようである。 問題文や提出されたプログラムは MySQL に保存されるが、ジャッジ時に使用するテストケースはジャッジサーバーに配置するようになっている。それ以外のデータはジャッジサーバーに置くことはないせいかジャッジサーバーは MySQL と通信しない。

使い方

環境立ち上げ

とりあえず docker compose でもろもろを立ち上げる。ここで DMOJ/online-judge が動いているコンテナを web コンテナ、DMOJ/judge-server が動いているコンテナを judger コンテナと以降呼ぶこととする。

git clone https://github.com/goropikari/dmoj_docker.git
cd dmoj_docker
make run

立ち上がったら http://127.0.0.1:8080/admin にアクセスする。 user name, password はともに admin。 問題の追加やコンテストの設定はこの admin 画面から行う。

admin画面

問題を追加する

問題文を追加

Problems -> Add で問題追加画面に行く

問題追加

add problem

必須項目は以下のもの

  • Problem code:
    • ^[a-z0-9]+$ を満たす任意の文字列
  • Problem name
    • 任意の文字列。日本語でも可。
  • Problem body:
    • 問題文。MathJax も使える。inline math は ~ で囲み、display math は $$ で囲む。
  • Problem types
    • 適当にタグを作って入れる
  • Problem group
    • 適当にタグを作って入れる
  • Points
    • 点数
  • Time limit
    • 実行時間制限
  • Memory limit
    • メモリ使用量制限
  • Language
    • サポートする言語。ここでチェックを入れた言語でもジャッジサーバー側が対応していない言語では提出できない。

任意項目

  • Publicly visible
    • 追加した問題が公開されるか否かをコントロールする。コンテストの中だけで表示させたい場合はチェックを外しておく。チェックを入れるとコンテスト前から公開された状態になる。
  • Language-specific resource limits
    • 言語ごとに制約を変えることができる

テストケースを追加

入力例と期待する出力を書いたテキストファイルを zip 圧縮したものと、得点や正誤判定方法を記した init.yml を作り judger コンテナの中に配置する。 ファイル構成としては以下のよう

helloworld/  # ディレクトリ名は problem code と名前を合わせる。ここがずれると正誤判定ができない。
├── helloworld.zip
├── init.yml
└── testcases
    ├── 1.in
    └── 1.out

ここで helloworld.zip は testcases 配下を圧縮したもの。

zip -j helloworld.zip testcases/*

zip を作った後は testcases は必要ないので消してしまっても良い。ただ、あとからテストケースに変更したりすることを考えるとそのまま残しておいたほうが良いとは思う。

init.yml

archive: helloworld.zip
test_cases:
- {in: 1.in, out: 1.out, points: 100}

これを judger コンテナの /home/judge/problems に配置する。

docker compose cp helloworld judger:/home/judge/problems

または

cp -r helloworld problems # problems を bind mount しているので docker cp でコピーしなくてもよい

これでジャッジできる準備が整った。 問題ページ (http://127.0.0.1:8080/problem/helloworld) に行き、Judge のところに ExampleJudge が表示されていれば設定は正しく行われている。(設定は正しいはずなのに Judge server が出てこないことがある。そのときは init.yml に改行を入れるなりすると認識されたりする)

得点は init.yml に書いたものでなく、問題を追加するときに設定した得点が表示される。init.yml に書いた得点は問題を解いたときに得られる得点ではなく、得点の重み付けと考えたほうがよいらしい。

例えば、問題の得点が 100 点で、init.yml が以下のようだったとする。

archive: helloworld.zip
test_cases:
- {in: 1.in, out: 1.out, points: 100}
- {in: 2.in, out: 2.out, points: 100}

このとき、1ケース目だけ正答すると 50 点獲得できるといった具合である。

判定方法を指定しなかった場合は、出力が期待するものと一致するかで正誤判定される。ただし末端改行の有無は正誤判定に影響しない。

その他の判定方法や部分点の設定方法などは公式サンプルがあるのでそちらを参考のこと。

docs/problem_examples at fcf7921050fa5d2fdca4a4c3440a7824f0ad38a1 · DMOJ/docs · GitHub

Submit solution から適当な言語を選んでプログラムを提出するとジャッジサーバーで判定される。

余談

利便性はあまり高くないと思うので詳しくは紹介しないが、問題ページにある Edit test data をクリックして遷移するページでもテストケースを追加できる。 上記同様に zip を作りアップロード -> Submit とすると Input file, Output file を選べるようになる。単に zip をアップロードしただけでは選択することはできない。

コンテスト作成

Admin 画面 -> Contests -> Add から情報を入力してコンテストを作成する。

Publicly visible にチェックをいれないと指定した人にしかコンテストが見れなくなってしまうのでそこだけ注意すればあとは必須項目を入れていくだけなので特段迷うところはないと思われる。

PROBLEMS の項目で OUTPUT PREFIX LENGTH OVERRIDE という謎の項目があるが、これは WA だったときに実際に出力されたものの頭 n 文字を表示するという項目である。入力をそのまま出力されるとテストケースがバレるので基本的には 0 のままにしておくのがよい。

保存した後にコンテストページにいくと、コンテストが生えていることが確認できる。

期限を過ぎた後は Virtual Contest として参加できるようにもなっている。

対応言語を追加する(失敗)

  • judge.yml に runtime 追加
  • Executor 作成
  • language_all.json に新しい言語を追加

とすれば行ける気がするが、system call 制限などでうまく追加できなかった。(Julia, Crystal を試して挫折した)

既存 runtime のものでも、judge.yml の設定が悪いのか使えない言語がある (特に JVM 系) ので正直何をしたらちゃんと動くのかコードをちゃんと追っていないので全くわからない。

github.com