AtCoder Scores

# はじめに
この記事は[AtCoder 関連サービス Advent Calendar 2018](https://adventar.org/calendars/2932) の 13 日目の記事です．

この記事では，私の管理しているサービス [AtCoder Scores](https://atcoder-scores.herokuapp.com) の紹介をします．当サービスの歴史を書いた後，実装がどうなっているかみたいなことを書きたいと思います．

# AtCoder Scores とは？
> [AtCoder](https://beta.atcoder.jp/) の（重み付き配点に対応した AGC 001 以降の）問題を点数順に並べる非公式サイトです

## 発端〜v0.1
<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">AtCoderの問題を難易度ごとに一覧表示する方法を考えてたけど、そもそも統一された配点の表記場所が無いっぽくて厳しそう</p>&mdash; 011_やざてん (@Yazaten) <a href="https://twitter.com/Yazaten/status/871026346641604608?ref_src=twsrc%5Etfw">June 3, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

このツイートを見て，やってやろうと思ったことがきっかけでした．ざっと最初のバージョンを書き，github.io で公開していました．（後述の理由により現在は移転していますが，当時の初版らしいデザインのページの再現ページは [ここ](https://atcoder-scores.herokuapp.com/old) から見られます．）

このツイートを理由にやざてんさんを「原案」の人としているんですが，そのせいで不具合報告などが飛んでいってしまっていたらすみません＞＜

## v1.0
Tsuta\_J さんや kobae964 さんからプルリクをいただいて，点数ごとの色分けや，ユーザ指定をして AC 数の表示や AC したエントリへの色付けなどの機能が追加されました．得点帯で表示を絞り込む機能もこの頃からありますね．

提出状況は [AtCoder Problems](http://beta.kenkoooo.com/atcoder) 様の API を利用していますが，クロスドメイン制約の関係で直接叩くことができなかったため，[YQL](https://developer.yahoo.com/yql/?guccounter=1) (Yahoo! Query Language) を利用していました．今ではヘッダがいい感じになっていていい感じになっていそう？（cf. [これ](https://github.com/kenkoooo/AtCoderProblems/issues/39)）

## v1.1
AtCoder Problems の API 自体は提出して数分で反映されるものの，YQL のキャッシュの関係で数時間から数日反映されない問題を抱えていました．同一のクエリを投げるとキャッシュが優先される仕様があることがわかったので，末尾にダミーのクエリを追加して対応しました．

ダミーのクエリは UNIX time を定数で割ったものを値としていて，数分間は同一のクエリを投げるようにしていました．（後々 [調べたところ](http://www.yqlblog.net/blog/2010/03/12/avoiding-rate-limits-and-getting-banned-in-yql-and-pipes-caching-is-your-friend/) によるとこれはよい方法らしくて，ランダムな値を付け加えるのはよくないとされていました．）

また，writer さんやコンテスト種別で絞り込んだりする機能が追加されました．

## v1.2
これで解決かと思われたものの，YQL で引っ張ってこれる JSON にはサイズの制限（1536 KB？ エントリ数の制限もあったかも）があるらしく，提出の多いユーザを 3 人ほど指定しただけであっぷあっぷになっていました（ダウンなんですけど）．

そこで，クロスドメイン制約を回避する別の方法を探しました．結局，サーバサイドで PHP を動かして `file_get_contents()` を叩けばよいことがわかりました．

しかし，github.io では静的なページしか表示できず，PHP を動かすことができなかったため，heroku に移転することになりました．heroku を選んだ理由は，なんとなくだったと思います．
無料でいろいろできてよいです（一部のことにはクレカが必要ですが，Vプリカなどでも対応可能でやさしいです）．

サーバサイドで PHP を動かせることになったので，Twitter API を叩いて進捗表の画像をツイートする機能などを追加しました．

この記事を書いていて気付いたんですが，v1.0 で書いた「ヘッダがいい感じになった」のはこの変更より前の時期ですね？（当時いろいろがんばったはずなんですが，うまくできていなかった説が濃厚になっていて険しい）

## v1.x
リファクタリングを兼ね，ほぼ全てのコードを一から書き直す，通称「AtCoder Scores RTA」を何度かやりました．

気づいたら本家 AtCoder 様の [便利リンク集](https://beta.atcoder.jp/posts/261) に載せていただいていました．

## v2.0 (current)
今までは，配点などのフィルタを変更するたびにページを更新していました．それは無駄だという指摘を受け，さすがにそれはその通りすぎるので，それを直す更新をしました．ここでも RTA をしました．記録を計測していないので再走させられそうです．

本家 AtCoder 様の UI に寄せてみようと思い立ち，二羃で遷移できるページャやお気に入り機能などを追加しました．

また，`/contact.*` ではなく `/contact` のようにアクセスできた方が気持ちいいと思い，`.htaccess` を調べてそれをやりました（これに関しての説明は飛ばします．言われれば書きます）．

あと本家の Clarification に少々近い見た目のものをお問い合わせページにおきました．

ざっくりとした歴史はこんな感じです．

# 実装面について
## 各種情報はどこから？
[トップページ](https://beta.atcoder.jp/) の告知を取得・パースし，配点や writer，コンテスト名などの情報を得ています．

コンテストが開かれたあたりのタイミングで必要なぶんの告知を再取得して JSON を生成しています．告知から必要なデータが得られなかった場合や未定だった場合は適宜手動で告知に似た形式のテキストファイルを手動で作ったりしています．

最初に作ったときは告知タイトルに「告知」と含まれているものをコンテストの告知として扱っていました（[Successful hacking attempt](https://beta.atcoder.jp/posts/77)）．

代替案として，`/*/standings/json` から取得するという方法があるんですが，部分点や writer などの情報が得られないことから避けていました．...が，チャート変更をして RTA するかもしれません．

また，問題名やコンテスト開催時期は該当するコンテストページから拾ってきています．

これらの処理は Python (Mechanicalsoup/BeautifulSoup) でやっていて，得たデータは JSON で保存しています．更新時に差分が見やすいように圧縮しない JSON で保存していたんですが，冷静に考えると，普段は圧縮して保存しておいて `diff` をするときだけ解凍すればいいですね．

### チャート変更案
- 配点は json からそのまま取得
  - これ正解者がいないとつらくない？ 別の json がある？
- writer や部分点は別途手動なりなんなりで保存
- 重み付き配点でないコンテストやマラソンのコンテスト名を保存
- コンテスト名はどうしよう？
  - `<title>...</title>` を適宜参照する？

## 表示の流れ？
現行バージョンでどう表示しているかを書いてみます．

本家サイトの [順位表を表示させるスクリプト](https://beta.atcoder.jp/public/js/standings.js) を参考にしています．

ユーザが指定された場合はそのユーザの情報（色や王冠など）を取得しますが，これはサーバサイドで済ませてしまっています．

保存しておいた JSON を Ajax で取得します．また必要に応じて AtCoder Problems 様の API も叩きます．重みつき配点でない問題への提出には関心がありませんので，間引いておきます．

その後，フィルタを適用して生き残った問題・提出からなるリストを作ります．

ページ情報（いま何ページめ？ 1 ページあたり何項目？）を元にそのリストの一部を切り出し，そこから HTML を生成します．

フィルタやページの変更があった場合は，必要なところからこれらの作業を再度行います．問題・提出のデータの取得は最初の一度のみで，以降はそれを使い回しています．

### 進捗表について
進捗表（重みつき配点の総和・問題数の総和のやつ）では，「解いた数 / 総数」に応じて部分的に色付けをしています．

ただ塗るだけではそっけない気がして，ぶわーっと伸びる感じにしていて，わりと気に入っているんですがどうですか？

徐々に変化させるのは `setInterval()` を使っていて，部分的に塗るのは CSS の `background-image` を使っています．また，どの程度のなめらかさで変化させるかは，経過時間に応じた係数を塗りたい量に掛けることで決めています．係数は，経過時間 $t$ に対して $\frac{50\tan^{-1}(t)}{\tan^{-1}(10)}+50 $ [%] としています（$t$ は離散値で，前計算した値を埋め込んでいます）．

### お気に入り機能について
問題名の横の☆をクリックするとその問題をお気に入りとすることができます．ブラウザの localStorage 機能を利用していて，ローカル環境にお気に入り問題のリスト（`["abc042_a", "agc001_b"]`のようなもの）を保存しています．時間経過によってこれらのデータが消えることはないですが，誤操作などで消してしまった場合の復旧がつらそうなので，そのうち見直すかもしれません．

「7 種類のラベルを好きにつけてキミだけのお気に入りを管理しよう！」と思ってたんですが，多くの人にとっては 1 つで十分なのかもしれません？

アクティブなお気に入り色がわかった方が嬉しいかなと思って，☆にマウスオーバーしたときにその色に光るのもこだわりポイントでした．

## Tips について
新機能をどの程度伝えたらいいかがわからなくて，Tips をランダムで一つずつ表示させています．

ページがごちゃごちゃしてきてうるさいと言われがちなので，整理していこうと思っています．

## 連携とか
[お問い合わせページ](https://atcoder-scores.herokuapp.com/contact) で書いた内容が私にどう伝えられているか考えたことはありますか？

これは Slack の Incoming webhook を利用しています．
自分用の Slack を作っていて，その #atcoder-scores チャンネルに連絡が来るようになっています．

簡単な設定を行っておくと，決められたページにアクセスすると，Bot にしゃべらせることができます（しゃべらせたい内容をクエリとして付けてアクセスします）．

[これ](https://qiita.com/namutaka/items/233a83100c94af033575) を見たりしました．

こんな感じです．

![slack-ss.png](https://qiita-image-store.s3.amazonaws.com/0/130767/77611bb9-1709-fe2b-4ee5-2d5351b3c0e7.png)

### 返信機能
不具合報告などで返信したい場面がどうしても出てくるので，それを最近追加しました．

全体公開が No な返信も簡単に実装してあります．

![Screenshot from 2018-12-12 23-27-21.png](https://qiita-image-store.s3.amazonaws.com/0/130767/f1cfdedd-4dfe-6f3c-f529-53f9e08e8739.png)

これの実装はアで，以下のようなことをしています．

- お問い合わせをした時点でキーを生成し，ローカルに保存
- そのキーを元にして回答一覧を取得

これは全体に公開するほどでないと思われる質問に対する処理なので，秘匿性を気にした作りにはなっていません．また，キーを削除したい場合は開発者ツールで `delLS('private_clar');` を叩けばいいと思います．

## 画像ツイート機能
[`html2canvas`](https://html2canvas.hertzen.com/) というライブラリがあり，特定の要素を元にして画像を生成してくれます．枠が若干ずれていたりしてアレなことがあるので，そのうちなんとかするかもしれません．

画像を生成した後は Twitter の認証（ここの説明は飛ばします．ググって得た程度の知識しかありません[^google]）をし，ツイートの内容を書いてもらいます．文字数のカウントは [`twitter-text`](https://github.com/twitter/twitter-text) を使っています．
[^google]: それは全てに対して言えそうな気がしますが．

## お遊び機能
<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">AtCoder Scoresの「ここではありません」機能好きすぎる<br>何気なく押したら出てきたので完全に行動を読まれててムフフってなってる <a href="https://t.co/Fp79ceviOl">pic.twitter.com/Fp79ceviOl</a></p>&mdash; prd🦍 (@prd_xxx) <a href="https://twitter.com/prd_xxx/status/1072829083958964224?ref_src=twsrc%5Etfw">December 12, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

えびちゃんはこういうやつがすきなので，いくつかこんな感じのものが含まれています．

わかりづらそうなものをネタバレしてしまうと，<font color="MintCream">ランダム選択のところで「NaN 問を選択」とすると難問が表示される</font>というのがあります．

## 今後実装されるかもしれない機能
[UI テストページ](https://atcoder-scores.herokuapp.com/ui) にあるような諸々（補完機能など）を追加するかもしれないです．

[精進部屋](https://atcoder-scores.herokuapp.com/training) で楽しいことができたらいいなと思っています．

先日何かしらの要因で AtCoder Problems 様がダウンしたことで当サイトの進捗表示もにゃーんになって，多くの人がつらそうにしていたのを見て提出情報の管理も自前でやろうとしたんですが，結局後回しになってしまいました（大変そうなので）．

# その他作ったもの
Userscript をいくつか作りました．

- [ac-score-table-ja](https://greasyfork.org/en/scripts/371819-ac-score-table-ja)
  - 英語版ページで配点表が表示されているとき，日本語版ページでも配点表を表示します．
- [ac-standings-notifier](https://greasyfork.org/en/scripts/372988-ac-standings-notifier)
  - コンテスト中に順位表を開いておくと，現在の順位などを数分おきに通知します．

# おわりに
いつも使っていただいてありがとうございます．「いつも使っています」のようなメッセージがくるとやっぱり嬉しいんですよね．

励ましの言葉をもらったときの気持ちとか，不具合報告でとげとげした言葉を受けたときのお気持ち（にゃーん的な意味で）とかって自分で作ってこそわかるものがあると思うんですよね．（作ったことない人もなにか作ってみよう！ 的なアレです．）

明日の記事は kenkoooo さん（！）の「AtCoder Problems 遅スギィ！自分高速化いいっすか？」です．いいゾ〜これ．

あと keymoon さんも早く書いてくださいね．気長に待っていますので．