Xcode de AtCoder

# 0. はじめに

2年ほど前から[AtCoder](https://atcoder.jp)に参加しています。はじめた頃は[VSCode](https://code.visualstudio.com)上で`Kotlin`を使用していました。しかし、もともとXcode＋Swiftでアプリを開発していたことと、AtCoderのジャッジシステムが[新しくなった](https://qiita.com/nak435/items/2cab8cc9918017b9564b)こともあって、昨年（2020年）4月から`Swift`でAtCoderに参加しています。
AtCoderのコンテスト中、テストケースの実行やコードの提出は、AtCoderのコンテストページから/へのコピペで行っていましたが、『**これら、自動化できるよね**』ということで、今回の記事に至りました。

##### AtCoderに関する自動化については、下記2つの記事が大変参考になります。

1. [AtCoderの提出自動化をする（Python）](https://qiita.com/Orangeat/items/7c44994d33b3ca83a6ea)

2. [atcoder初心者こそ環境構築しよう！(atcoder-cli,online-judge-toolsのインストール、使い方）](https://qiita.com/Adaachill/items/3d4ddad56c5c2cc372cd)

Swift言語においても、[VSCode](https://code.visualstudio.com)やコマンドラインでコンパル&実行するなら、上記の記事で十分ですが、今回はXcodeのコンソールアプリのプロジェクトを使い回すことを前提にしています。つまり、コンテストや問題ごとにソースファイルを分けずに、常に一つのXcodeプロジェクトで実施しようというものです。これが**ベストプラクティス**とは言いませんが、自分は**この環境でAtCoderに参加したい**ということです。

# 1. 今回のゴール

Xcode＋SwiftのコンソールアプリにてAtCoderの解答コードを作成し、**サンプルテストケースの自動実行**とAtCoderへの**ソースコードの提出を自動化する**こと。


# 2. 準備

[前出](#atcoderに関する自動化については下記2つの記事が大変参考になります)の自動化記事を参考に、`online-judge-tools`と`atcoder-cli`をインストールしておきます。


# 3. 環境

### a) Xcodeのコンソールアプリのプロジェクト

Xcodeで 新規プロジェクト(New **→** Project) **→** macOS **→** Command Line Tool と進み、「プロジェクト名」やプロジェクトの「格納先ディレクトリ」を指定して作成したプロジェクトは、標準入力/標準出力から入出力する**コンソールアプリ**となります。変更しない限り**プロジェクト名＝プログラム名**です。

### b) ソースファイルの場所の確認

上記で指定したプロジェクトフォルダ配下に`main.swift`として作成されます。このファイルにAtCoderの問題を解くコードを書きます（いわゆるコーディング）。
AtCoderへ提出するコードがこれになるので**フルパス**をメモしておきます。

### c) ビルドして生成されるバイナリファイルの場所の確認

Xcodeで**ビルド**すると、**実行ファイル**が生成されます。このファイルの場所は、Xcodeのプロジェクトナビゲーターの`Products`の下にあるプロジェクト名と同名の実行ファイルをクリックすると、フルパスを知ることができます。
![スクショ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/2f47984a-dc5d-1760-0de8-cb56beff8a20.png)
また`Show in Finder`を選べば、その場所のファインダーが開きます。
ローカル環境でテストするバイナリファイル（実行ファイル）がこれになるので**フルパス**をメモしておきます。
Xcodeで`Run`(実行)すると、このプログラムが起動され、Xcode上のコンソールで入力や出力されます。もちろん、ターミナルを開き、この実行ファイルを直接起動することもできます。


# 4. 自動化

AtCoderの2つの自動化に対応します。

1. テストの自動化
2. ソースコードの提出

## 4.1 テストの自動化

AtCoderのコンテストでは各問題ごとに例題として数件のテストケースが提供されます。『**この入力を与えると、こう出力されたら正解**』といった例です。この例題の入力と出力をもとに、作成したプログラムの動作確認を**自動で行うこと**が目的です。
これには[前出](#atcoderに関する自動化については下記2つの記事が大変参考になります)の`online-judge-tools`(**oj**)を使用します。**oj**はターミナルから投入するコマンドです。

### a) AtCoderへログイン

下記のコマンドを投入します。AtCoderへのログインは、コンテストごとに最初に一回実行しておきます。

```shell
$ acc login
  ? username: ユーザID
  ? password: パスワード
$ oj login -u ユーザID -p パスワード https://atcoder.jp/
```

### b) テストケースのダウンロード

サブフォルダ作成とテストケースのダウンロードをまとめて行ってくれる`acc new`を使用します。コマンドの引数にコンテストIDを指定します。
この例では、コンテストID：ABC190

```shell
$ acc new abc190
```

すると、カレントディレクトリに、以下のように`コンテストID`フォルダが作成され、その配下に問題タスクごとのテストケースをダウンロードして入力と出力ファイルが作成されます。

```text
.
└── ABC190
    ├── a
    │   └── tests
    │       ├── sample-1.in
    │       ├── sample-1.out
    │       ├── sample-2.in
    │       ├── sample-2.out
    │       ├── sample-3.in
    │       └── sample-3.out
    ├── b
    │   └── tests
    │       ├── sample-1.in
    │       ├── sample-1.out
    │       ├── sample-2.in
    │       ├── sample-2.out
    │       ├── sample-3.in
    │       └── sample-3.out
    ├── c
    │   └── tests
    │       ├── sample-1.in
    │       ├── sample-1.out
    │       ├── sample-2.in
    │       ├── sample-2.out
    │       ├── sample-3.in
    │       └── sample-3.out
    ├── contest.acc.json
    ├── d
    │   └── tests
    │       ├── sample-1.in
    │       ├── sample-1.out
    │       ├── sample-2.in
    │       ├── sample-2.out
    │       ├── sample-3.in
    │       └── sample-3.out
    ├── e
    │   └── tests
    │       ├── sample-1.in
    │       ├── sample-1.out
    │       ├── sample-2.in
    │       ├── sample-2.out
    │       ├── sample-3.in
    │       └── sample-3.out
    └── f
        └── tests
            ├── sample-1.in
            ├── sample-1.out
            ├── sample-2.in
            └── sample-2.out
```

##### `acc contest|tasks`コマンドでAtCoderのURLが取得可能です。

```shell
$ acc contest abc190
AtCoder Beginner Contest 190  https://atcoder.jp/contests/abc190

$ acc tasks abc190
A  Very Very Primitive Game  https://atcoder.jp/contests/abc190/tasks/abc190_a
B  Magic 3                   https://atcoder.jp/contests/abc190/tasks/abc190_b
C  Bowls and Dishes          https://atcoder.jp/contests/abc190/tasks/abc190_c
D  Staircase Sequences       https://atcoder.jp/contests/abc190/tasks/abc190_d
E  Magical Ornament          https://atcoder.jp/contests/abc190/tasks/abc190_e
F  Shift and Inversions      https://atcoder.jp/contests/abc190/tasks/abc190_f
```




### c) テスト

これまでは準備で、やっとテストです。
下記コマンドで実行します。各問題のサブディレクトリに移動して実行します。例えば、A問題はディレクトリaを指定します。

```shell
$ cd コンテストID
$ cd a
$ oj test -d tests -c "実行ファイルパス"
```

結果は、下記の通り。それぞれのテストケースが自動でテストされて出力と照合して**AC**となっています。
[実行ファイルパス](#c-ビルドして生成されるバイナリファイルの場所の確認)は固定のため、環境変数**SWIFTEXE**に設定しています。

```shell
$ cd ABC190
$ cd a
$ oj test -d tests -c "$SWIFTEXE"
[INFO] online-judge-tools 11.1.1 (+ online-judge-api-client 10.8.0)
[INFO] 3 cases found
time: illegal option -- f
usage: time [-lp] <command>
[WARNING] GNU time is not available: time

[INFO] sample-1
[INFO] time: 0.035278 sec
[SUCCESS] AC

[INFO] sample-2
[INFO] time: 0.009566 sec
[SUCCESS] AC

[INFO] sample-3
[INFO] time: 0.009297 sec
[SUCCESS] AC

[INFO] slowest: 0.035278 sec  (for sample-1)
[SUCCESS] test success: 3 cases 
```

## 4.2 提出の自動化

下記コマンドでソースコードを提出します。コマンドの引数で[前述](#acc-contesttasksコマンドでatcoderのurlが取得可能です)の問題ごとのURLを指定します。
この例では、
　コンテストID：ABC190
　問題ID：ABC190-A (A問題)
コマンドの引数の意味は、--no-guess：推測しない、-y：応答確認しない、--no-open：提出後にAtCoderのサイトを開かない、 -l：言語を指定、です。 

```shell
$ oj submit --no-guess -y --no-open -l Swift https://atcoder.jp/contests/abc190/tasks/abc190_a "ソースファイルパス"
```

結果は以下の通り。
[ソースファイル名](#b-ソースファイルの場所の確認)は固定のため、環境変数**SWIFTSRC**に設定しています。

```shell
$ oj submit --no-guess -y --no-open -l Swift https://atcoder.jp/contests/abc190/tasks/abc190_a "$SWIFTSRC"
[INFO] online-judge-tools 11.1.1 (+ online-judge-api-client 10.8.0)
[WARNING] cannot guess URL since the given file is not in the current directory
: : :
: : :
[INFO] chosen language: 4055 (Swift (5.2.1))
[WARNING] the problem "https://atcoder.jp/contests/abc190/tasks/abc190_a" is specified to submit, but no samples were downloaded in this directory. this may be mis-operation
[INFO] sleep(3.00)
: : :
: : :
[SUCCESS] result: https://atcoder.jp/contests/abc190/submissions/19897304
[INFO] save cookie to: /Users/USERNAME/Library/Application Support/online-judge-tools/cookie.jar

```

ソースコードの提出はできましたが、想定外のワーニングが出ています。カレントディレクトリにソースファイルが置かれていないことが原因のようです。
とは言え、今回の目的は問題ごとにソースファイルを分けないので、元のソースファイルをカレントディレクトリにコピーしてこのファイルを指定するようにしました。するとワーニングがなくなります。

```shell
$ cp $SWIFTSRC ./
$ oj submit --no-guess -y --no-open -l Swift https://atcoder.jp/contests/abc190/tasks/abc190_a main.swift
[INFO] online-judge-tools 11.1.1 (+ online-judge-api-client 10.8.0)
[INFO] read history from: /Users/USERNAME/Library/Caches/online-judge-tools/download-history.jsonl
[INFO] found urls in history: https://atcoder.jp/contests/abc190/tasks/abc190_a
[INFO] problem recognized: AtCoderProblem.from_url('https://atcoder.jp/contests/abc190/tasks/abc190_a'): https://atcoder.jp/contests/abc190/tasks/abc190_a
[INFO] code (13321 byte):
: : :
: : :
[SUCCESS] result: https://atcoder.jp/contests/abc190/submissions/19897431
[INFO] save cookie to: /Users/USERNAME/Library/Application Support/online-judge-tools/cookie.jar
```

例えば、問題Aから問題Bへの切り替えは、サブディレクトの移動だけですみます。

```shell
$ cd ../b
```

# 5. コマンドレス化（CUI化）

上手く実現できる方式がなかなか見つからないのでGUI化を見送って、上記までの種々のコマンドをシェルスクリプトに閉じ込めて1文字のキャラクタをタイプするだけで実行可能にしました。

```shell
$ . ~/atc コンテストID
```

スクリプトの中身は、**①**login実行、**②**環境変数等を設定、**③**`acc new コンテストID`の実行、**④**下記コマンドのfunction定義、です。

| コマンド | 意味 | 内容 |
|:-:|:-:|:-:|
| a  | A問題を選択  | サブディレクトをA問題に切り替え  |
| b  | B問題を選択  | サブディレクトをB問題に切り替え  |
| c  | C問題を選択  | サブディレクトをC問題に切り替え  |
| d  | D問題を選択  | サブディレクトをD問題に切り替え  |
| e  | E問題を選択  | サブディレクトをE問題に切り替え  |
| f  | F問題を選択  | サブディレクトをF問題に切り替え  |
| t  | テスト  | 選択中の問題のテスト  |
| s  | 提出  | 選択中の問題のソースコードを提出  |

コンテスト終了後に、logoutは手動で実行します。

# 6. おわりに

[前出](#atcoderに関する自動化については下記2つの記事が大変参考になります)記事の真似に過ぎませんが、実際のコンテストに数回参加し実践しましたが、超絶便利になりました。自画自賛ですが、AtCoderされている方にはぜひ**自動化**を**お勧めします**。

ジャッジ結果がモーダルで表示される、stanicさんが開発したプラグインも大変便利です。こちらを参照してください。⇒ [AtCoderResultNotifier - AtCoderの提出結果を通知するUserScriptを作成しました](http://satanic0258.hatenablog.com/entry/2018/08/16/215949)

~~これを使うために、SafariからGoogle Chromeに変えました。~~ [こちらのuserscripts](https://apps.apple.com/us/app/userscripts/id1463298887)をSafariにインストールすれば、上記のプラグインがSafariでちゃんと動きました。[AtCoderResultNotifier](https://greasyfork.org/ja/scripts/371225-atcoderresultnotifier)の「スクリプトをインストール」ではインストールできないのでちょっと手こずりました。
> New Remoteを選び、Enter remote url:に`https://greasyfork.org/scripts/371225-atcoderresultnotifier/code/AtCoderResultNotifier.user.js`を入れてOK。ファイルが読み込まれたら、右下のSaveをクリック。

みなさんの参考になれば幸いです。以上


# 7. 追加情報（2021.12.26追記）

## 7.1 `online-judge-tools`のアップデート

アップデートの案内に従ってコマンドを投入するだけです。

```sh
$ pip install --upgrade pip           # pip自身のアップデート
$ pip3 install -U online-judge-tools
$ pip3 install -U online-judge-api-client
```

## 7.2 `gnu-time`のインストール

```diff_shell
$ oj test -d tests -c "$SWIFTEXE"
 [INFO] online-judge-tools 11.5.1 (+ online-judge-api-client 10.10.0)
 [INFO] 2 cases found
+[WARNING] GNU time is not available: gtime
+[HINT] You can install GNU time with: $ brew install gnu-time

$
```

アップデート後、上記のように`gtime`のインストールの案内が出てきたので`gnu-time`をインストールしました。

```sh
$ brew install gnu-time
```

すると、テスト時のWARNINGが出なくなりスッキリしました。

```sh
$ oj test -d tests -c "$SWIFTEXE"
/Users/USERNAME/Library/Developer/Xcode/DerivedData/AtCoder-fbvzrmhzrmdoyxeaszbdjifcteet/Build/Products/Debug/AtCoder -d tests
[INFO] online-judge-tools 11.5.1 (+ online-judge-api-client 10.10.0)
[INFO] 2 cases found

[INFO] sample-1
[INFO] time: 0.013775 sec
[SUCCESS] AC

[INFO] sample-2
[INFO] time: 0.010516 sec
[SUCCESS] AC

[INFO] slowest: 0.013775 sec  (for sample-1)
[INFO] max memory: 2.188000 MB  (for sample-1)
[SUCCESS] test success: 2 cases
$
```

<hr>

### 参考情報

[online-judge-tools/oj](https://github.com/online-judge-tools/oj/blob/master/docs/getting-started.ja.md)

[atcoder-cli チュートリアル](http://tatamo.81.la/blog/2018/12/07/atcoder-cli-tutorial/)