背景
研究室の学生に簡単なアルゴリズムを使いこなせるようになってほしいなー。研究に役立ててほしいなー。
でもただAtCoderの問題を例にあげて解説するだけなのもつまらないなー。
競技プログラミング風にしたらゲーム性あって面白いかなと思ったので、ジャッジを作ることにしました。
前提
- 今回参加するのは15人くらい、みんな対面
- 研究室の人はみんな優秀なので基本的なLinuxコマンドは使える、ターミナルは怖くない
- 全員PythonかC++の文法は知っている、環境構築も済んでいる
- 自分以外競プロの経験はないがほとんどの人は基本情報 or 応用情報を持っている
- 研究室の人はみんな良い人(ここ大事!)
- 1時間くらいで二分探索と動的計画法を知ってもらう(時間が足りません!ということで解説は二分探索だけしました)
- 20時間ほどで作りたい
- 実装するのはめんどくさい
AtCoderから問題を選定してくればジャッジを作る必要ないじゃないか、という声が聞こえてきますが、もう二度と使わないであろうサイトに1時間のためだけに会員登録をさせるのは良くないと思いました。(体験が最悪だからという意味ではなく、単に自分以外は競プロ自体にはさほど興味がないだけという意味)
今回の相棒
実装がめんどくさいのでClaudeを召喚しました。
完成イメージは自分の中になんとなくあったので、必要最低限だと思われる機能から順に足して作っていきました。
バイブコーディングの所感としては、「とりあえずWebサイト作る」とか「一旦小規模に作る」とかはAIエージェントにお任せしても十分な性能のものができる印象。
一方で、小規模な開発であっても丸投げすると非効率的な処理を行う部分を作ってしまうところがあり、実装の前に「このデータ構造を使って」みたいなことを言って実装方針を誘導すると割と良い感じ。
AtCoderだと緑くらいまでの知識がしっかり身についていればアルゴリズム力が役立つ場面はきっと来ると思う。水以上でも、「自分の知らないアルゴリズムでここ高速化できそう?調べてみるか。」みたいな場面があると思う。
AIエージェントが進化したら最初からスケーラビリティあって実行速度が良さげな実装をしてくれるのかな?
構成
実際の完成物はこちら。
ユーザ(クライアント)側
https://github.com/ayuna-stpyko/algorithms_for_lab
ジャッジ(サーバ)側
https://github.com/ayuna-stpyko/algorithms_for_lab_judge_system
試しに動かしてみたい方はREADMEに従ってセットアップすると大体うまくいきます。
一部ライブラリのインストールの案内を忘れているので、エラーが出たら適宜インストールでお願いします。
アルゴリズムを教えつつも競技性を持たせる、ヒントを出しやすい環境にする、実装はできるだけ楽にしたいという観点から以下のような要件を満たすことにしました。
- PythonとC++に対応する
- インタラクティブ問題にも対応する
- ユーザは簡単にコードを提出できる
- サーバ側で全ての提出を見られるようにする(ここが違う、といったヒントをすぐ出せるように)
- サーバにログを残す
- 順位表ページを作りリアルタイムで動くようにする
- ヒントを見られるようにする
- セキュリティは最低限で、ユーザの良心に任せる(頼る)
今回は小規模に一時的なサーバを立てるだけですし、自分もその場にいるので大抵のことはおそらくその場でなんとかなります。が、最後のが一番まずくて、今回の実装ではコードをサーバ側で安全な環境に隔離することなく実行するので、PythonやC++で実行可能な範囲で任意のことができてしまいます。ひどい脆弱性。ほら、MVPっていうじゃないですか。それです、それ。
Dockerを導入する変更を加える時間がなかった〜
import subprocess
subprocess.run(["rm", "-rf", "/"])
を絶対にしないでね![]()
結果的に当日はみんなちゃんと変なことはしませんでした。lsさえした人もいません。助かった…
テストのために初めてリモートサーバを起動して手元からいくつかコードを提出していたとき、しばらくしてそのサーバと通信ができなくなったことがありました。最初はセキュリティゆるゆるすぎたかと思い少し焦った。たまたまリモートが落ちただけだった。危なかった。
githubからコードをご自由に使っていただいても良いのですが、大規模にやるならそのあたりは改修しておいてください。競プロっぽいことをちょっとだけ体験してもらうには向いていると思います。
ちなみにどんな指摘をもらっても「時間がなかったので実装してないです。修正してないです。」しか言えないです。自分でもある程度は抜けを認識しています。
ユーザ側
提出
コードを書いてmake test main.pyかmake test main.cppと打てば提出完了!
想定ユーザは競プロ未経験者ですが、提出方法に戸惑うのは非本質なので1コマンドで提出できるようにしました。GUIで提出するような画面を作る時間なぞもちろんない。
コマンドが打たれたらPythonが動き始め、サーバの負荷軽減のため、提出前に拡張子の確認などのプリチェックをします。
そしてHTTP POSTでサーバにどーん。
サーバがジャッジをしてくれます。
Correct Answerならこんな感じの出力↓がターミナルに出ます。
==================================================
JUDGE RESULT
==================================================
Status: CORRECT_ANSWER
🎉 Congratulations! Your solution is correct!
Execution Time: 50ms
Memory Usage: 100KB
Test Cases: 15/15 passed
==================================================
他にもWAとかTLEとかあります。
ヒント取得
問題ディレクトリでmake hintと打てばヒントゲット!
標準入出力の方法や文法のうっかり忘れはアルゴリズムを学ぶという目的の上では非本質&&1時間だと自分でも解き切れるか微妙な問題量と難易度感のものを用意してしまったので、ヒント機能を作りました。
競プロなら前提 of 前提ですが、今回は競プロそのものをやらせたいわけではないので。
私は不親切なので、ヒントは1問につき1つしか用意しませんでした。
私は親切なので、同じ問題のヒントを2回以上要求してしまっても1回しかペナルティが加算されないようにしました。
githubにはAIエージェントが出してきた適当なヒントがのっていますが、実際には私が事前に実装した正解コードから一部を削除したものをヒントとして、穴埋め方式で解けるようにしました。
コード丸々は長いので、ヒントはターミナルにそのまま出力されるのではなくhint.txtに保存されます。
サーバ側
メイン部分はFlask、順位表などのデータベースはSQL。
一応Djangoや他の言語でも鯖立てしたことはありますが、がっつり作るつもりはなく実行速度もあまり求めていなかったので自分が一番慣れている言語で軽量なフレームワークを選びました。
ついでになぜか趣味でOpenAPIに対応しておきました。
また、提出にはAPIキーを必要としていて、これで誰の提出なのかを判定するのと同時に万が一の外部からの攻撃コード提出を防ぎます。ログイン機能をつけても良い(つけた方が良い)のですが、なくても動くので最低限に。内部ネットワークにしか公開しなかったので、大丈夫だと思いますが。
ちなみにめんどくさいので研究室ではAPIキーの一覧表を全員宛てに送りました。
みんな、他の人になりすまして提出しないでね。
問題の提出のときの動作
- ユーザから提出コード、問題名などを受け取る
- APIキーや問題名などのバリデーションをする
-
コードを保存する
自分の方からもコードを読めるようにして、誤ったコードを提出した人に口頭でヒントをあげます。 -
他に採点中のものがなければロックをかける
サーバは一度に一問しか採点しないようロックをかけるので、提出が集中した場合には採点待ちが発生します。これで実行時間の公平性を持たせます、一応。 - 順位表に提出中の印をつける
-
インタラクティブ問題ならパイプを繋いでジャッジと会話する
会話を行うC++コードをコンパイルして使用します。 -
非インタラクティブ問題なら入出力ファイルを読み込み実行結果から判定する
.inファイルと.outファイルを読んで出力との完全一致で判定をします。
小数の誤差などには非対応です。 -
ジャッジの結果を順位表に反映させてロックを外す
提出をすると順位表のステータスが変わって、採点結果に応じてペナルティがついたり順位が上がったりします。
ここはICPCと似たような見た目と仕様をしているはず。
順位が上がるとき、なんとにゅいーんと動きます。楽しいね。
順位表はこんな感じです。

特に文章はAIが考えた感満載ですね。もうちょっと自然にしようとして変えるのを忘れていました。
今回適用したルール
configで定数を変更することで、コンテスト時間やWA時のペナルティ時間、各問題のテストケース数などを設定できるようにしました。
githubにあるのとは若干ルールを変更し、実際に適用したルールをまとめるとこんな感じ。
- 得点計算の方法はほぼICPCと同じで、解いた問題数が多くて解くまでの時間が短ければ強い
- WAなどのペナルティは1回につき3分
- ヒントを要求した場合は1問につき20分
間違えること自体は歓迎だったのでペナルティは比較的軽めにしました。一方、ノーヒントで解ける力がある人も歓迎なのでヒントのペナルティは重めにしました。
やってみて
1位の人で3問正解でした。
概ね想定通りです。もう1時間くらいやったら面白そうだと思ったのですが、後にクリスマスパーティーが控えていたのでやめました。
時間までに解けなくて悔しくなり終わった後に質問をしてくれた人がいて嬉しかったです。悔しいとか楽しいとかそういう感情って一番勉強のモチベになるものだと思います。
ジャッジシステムの実装については、AIエージェントのおかげでライブラリの仕様を調べる時間などの短縮はできました。
一方で、いきなりコーディングをしようと指示出しすると思わぬ実装や余計なことをしてくるので、事前に話し合いをしようという教訓を得ました。これは人間に仕事を頼むときも同じですね。
今回は時間がなかったのでザルセキュリティのものを作りましたが、yukicoderやmofecoderなどの有志が作成したジャッジシステムはより安全でより多くのユーザからのアクセスを捌くことができる作り方をしているはずですごいなあと思い、感謝しながら使うことにします。