今日からあなたも競プロ出題者！ブラウザ上で動作するジャッジシステム OnBrowserJudge

# 要約

- ブラウザ上で動作するジャッジシステムを作りました。
  - そのため静的ファイルを置ける場所さえあれば、他にサーバーやアカウント等を用意する必要はありません。
- 言語は現在 Ruby, Python, JavaScript に対応。
  - [Ruby の例](https://nodai2hitc.github.io/onbrowserjudge/examples/collatz_ruby_jp.html)、[Python の例](https://nodai2hitc.github.io/onbrowserjudge/examples/collatz_python_jp.html)、[JavaScript の例](https://nodai2hitc.github.io/onbrowserjudge/examples/collatz_javascript_jp.html)
- 自由度が高く、さまざまな形式の問題が作成可能です。
  - 例えば AtCoder のように標準入力・出力を用いる問題だけでなく、LeetCode のように関数を作成する問題や、事前に代入された変数を用いる問題なども作れます。
    - [関数作成問題の例(Ruby)](https://nodai2hitc.github.io/onbrowserjudge/examples/collatz_ruby_method_jp.html)、[変数が事前に代入される問題の例(Ruby)](https://nodai2hitc.github.io/onbrowserjudge/examples/collatz_ruby_var_jp.html)

# 今、競プロが熱い！

熱いですよね！というわけで熱いという前提で話を進めます。そんな今最高に熱い競技である競技プログラミングを始める人が増えています。

ところで競技プログラミングを始めた人の中には、出題する writer の側に興味を持った人も居るのではないでしょうか。ですが、競プロ出題者になるのは簡単ではありません。

## 競プロ出題者になるには

まず、競プロ国内最大手サービスである [AtCoder](https://atcoder.jp/) には、利用者が自由に出題をできるシステムは残念ながらありません（よね？）。

[yukicoder](https://yukicoder.me/) であれば出題システムがあります。ですが作った問題は全体公開されるため、いい加減な問題を作るわけには行きません。そのため「仲間内でちょっとした問題を出したい」という用途には使えないでしょう。

このように既存競プロサービスを利用するのは難しいので、そうすると「自分でジャッジシステムを作る」という方法が考えられます。ですがこれも簡単ではありません。完全に１からシステムを作るのは言うまでもなく大変ですが、既存のオープンソースのジャッジシステム（調べたら [いくつか](https://github.com/DMOJ/online-judge) [ありました](https://github.com/HarrisonOwl/OwlJudge)）を利用するのも知識が必要です。また、ジャッジシステムは「提出された任意のコードを実行しなければならない」という性質上セキュリティ面で大きな危険を孕んでいます。生半可な知識で手を出すのは止めておいたほうがいいでしょう。

他には、自動ジャッジシステムを使わずにメール等でコードを提出してもらう方法も考えられますが、それは運用が大変です。

そこで、ブラウザ上で動作する自動ジャッジシステムを作成することで、誰でも比較的簡単に出題ができる仕組みを作ってみました。

# 必要なもの

- 静的ファイルを置ける場所
  - たとえば、GitHub Pages を利用すれば GitHub アカウントさえあれば大丈夫です。他にも各種無料レンタルサーバー等でも大丈夫なはずです。
- HTML に関するちょっぴりの知識
  - テンプレートを書き換えるだけであれば、ほとんど知識は不要です。
- 競技プログラミングへの**熱い想い**

# 問題を作ってみる（テンプレートを用いた簡易版）

まず [GitHub 上](https://github.com/nodai2hITC/onbrowserjudge) から、必要なファイルをダウンロードします。（とりあえず右上の「Code」→「Download ZIP」で全部まとめてダウンロードしておけばいいでしょう。）

テンプレートを用いて作る場合、以下の 4 つのファイルが必要です。

- `template_言語名_ja.html`
- `onbrowserjudge.js`
- `言語名.js`
- `style.css`

## 書き換える

書き換える必要があるのは、`template_言語名_ja.html` だけです。ファイル名も変更しておきましょう。

### タイトル・問題文・制約・入力・出力

自由に HTML を書き換えてください。テンプレートの場合、[MathJax](https://www.mathjax.org/) を読み込んでいるので LaTeX 形式等を用いて数式も利用できます。とはいえ読めれば別に素の HTML だけで書いても何の問題もありません。

### 入力例・出力例

```html:template_言語名_ja.html
<pre id="sample1_input"  class="sample">123</pre>
<pre id="sample1_output" class="sample">456</pre>
```

…のように、`～_input` `～_output` という id を持った pre 要素で入力例・出力例を記述してください。「～」の部分は何でも大丈夫ですが、_input と _output で同じものを使う必要があります。

### テストケース

```html:template_言語名_ja.html
<pre id="testcase1_input"  class="testcase">123</pre>
<pre id="testcase1_output" class="testcase">456</pre>
```

こちらも入力例・出力例同様に、`～_input` `～_output` という id を持った pre 要素で入力値・出力値を記述してください。テンプレートの場合、`testcase` という class 属性を持たせると表示されなくなります。

これだけで書き換える場所は終わりです。実際に実行してみてください！（どこかにアップしなくても、ローカルで試しに実行することもできます。）

# より高度な使い方

## 正解したら何かする

```javascript:template_言語名_ja.html
OnBrowserJudge.congratulations = () => { alert("大正解！") }
```

のように、`OnBrowserJudge.congratulations` に関数を設定することで、正解した際にその関数を実行させることができます。

これを利用することで、「正解したら次の問題へのリンクを表示する or 自動で飛ぶ」「正解したらキーワードを表示し、そのキーワードをメール等で送らせることで正解したかどうかをチェックする」等の使い方ができます。

## 穴埋め問題を作る

テンプレートを用いる場合であれば、textarea#txt-editor 要素内に（不完全な）コードを書いておくことで、穴埋め問題を作ることなどもできます。

```html:template_ruby_ja.html
<textarea id="txt-editor">
a = gets.to_i
b = 
# 上の行を書き換えて、正しい出力がされるようにしよう！
puts b
</textarea>
```

## HTML に必ず必要なもの

テンプレートを用いない（あるいはテンプレートを大幅に書き換える）場合、その HTML に以下のものが最低限必要です。

- `<script src="onbrowserjudge.js"></script>`
- `<script> OnBrowserJudge.workerFile = "言語名.js" </script>`
  - この 2 つは、この順序で書く必要があります。
- 1 組以上の `～_input` `～_output` という id を持った pre 要素
- `run` という id を持った button 要素
- `OnBrowserJudge.getProgram()` という名前の関数
  - 実行するべきプログラムを取得する関数です。テンプレートではエディターに [CodeMirror](https://codemirror.net/) を用いているので そこから取得していますが、これを書き換えれば [Ace](https://ace.c9.io/) 等 他のエディターを用いることができます。

以上のものさえあれば、他の部分は自由に書き換えてしまって大丈夫です。

## 問題形式を変更する

AtCoder 等では標準入力・標準出力を用いて入出力が行われます。それはこのシステムも同じなのですが、`OnBrowserJudge.process(program, casename, input)` という関数を定義して実行するプログラムを書き換えることで、違う方法で入出力を行う問題を作ることができます。

例えば、Ruby であれば

```javascript:template_ruby_ja.html
OnBrowserJudge.process = (program, casename, input) => program + `

puts foo(gets.to_i)
`
```

などとしてやれば、`foo(int)` というメソッドを適切に定義する問題にすることができます。（もちろん Ruby 以外でも同様にできます。）

他にも、以下のようにすれば「あらかじめ変数 n に値が代入されているものとして、それ以後のプログラムを書く」という問題にもできます。

```javascript:template_ruby_ja.html
OnBrowserJudge.process = (program, casename, input) => `
n = gets.to_i
` + program
```

ただし こうした変更をした場合は、HTML 上に表示される「入力例」と 実際に使われる入力値が異なってしまうので、下の例のように 2 つ pre 要素を作る必要があるでしょう。

```html:template_言語名_ja.html
<pre id="sample1_dummy_input" class="sample">foo(123)</pre><!-- ←表示される入力例 -->
<pre id="sample1_input" class="testcase">123</pre><!-- ←実際に使用する入力値 -->
```

## 答えが複数ある問題や、許容誤差のある問題を作る

出力例と実際の出力が等しいかどうかを `OnBrowserJudge.assert_equal(expected, actual)` という関数で確認しています。

初期設定では この関数は `expected == actual.trimEnd()` 、つまり出力例と実際の出力が末尾の空白を除いて完全に一致した場合のみ AC となりますが、この関数を書き換えることで 答えが複数ある問題や許容誤差のある問題を作ることができます。

# このシステムの欠点

## 実行環境の性能次第で、TLE になるか否かが変わる

ブラウザ上で動く以上、そのブラウザを動かすマシンの性能によって TLE になるか否かが変わってしまいます。良いマシンを持っている人ほど有利になってしまいます。

## テストケースが見れてしまうなど、いろいろ不正が可能

テストケース用の入出力が HTML に書かれているので、それを見て嘘解法を書くことができてしまいます。ですから本格的な用途には使うことはできないでしょう。

# やりたいこと

やはり競技プログラミングといえば C++ が主流なので、C++ も使えるようにしたいところです。

C については C インタプリタ「[PicoC](https://github.com/jpoirier/picoc)」の [WASM 版](https://github.com/KritR/picoc-js) を見つけたので行けそうな気がしますが、C++ についても似たようなものはあるのでしょうか…

# 使用技術

- Python 版：[Pyodide](https://pyodide.org/en/stable/)
- Ruby 版：[ruby.wasm](https://github.com/ruby/ruby.wasm)

ちなみに ruby.wasm が正式サポートされる Ruby 3.2 は、12/25 リリース予定です！

# 終わりに

このシステムを使った問題を作成したら（必須ではありませんが）ご連絡いただけると嬉しいです！