task4233のめも

書きたいことをつらつらと

AtCoder向けのUserscriptを書いた話

はじめに

私は春に競プロを布教していた人です。

この記事は「初心者でもとりあえずやってみたらなんとかなったよ」ということを伝えたくて書きました。

とはいえ、何の根拠もなく語っていても「は?」と思われるので、競プロサイトであるAtCoder向けのUserscriptを書いた時の話をします。

かしこまった記事は書けないので、ですます調はここら辺で終わりにして気楽に書いていこうと思います。

語句の説明

  • 競プロ

競技プログラミングのこと。与えられた問題を「速く、そして正確に」解くパズルのようなもの。

→ オンラインで参加できる競プロのサイト。月に数回コンテストが開かれる。社長が面白い。

  • Userscript

Wikipediaによると
「Userscriptは通常JavaScriptで書かれ、Webページを編集するソースコード
などと書かれている。ChromeではTempermonkeyFirefoxではgreasemonkeyによる拡張機能により動作可能。Userscript自体はgreasyforkというサイトで配布されている。

インストールに関する記事は以下を参照されたい。 qiita.com


もくじ

作ったもの

こちら。 AtCoderScoreHider

説明はリンク先の概要を参照されたい。

なぜ作ったのか

結論から言うと、私が欲しい機能だったからだ。

というのも、AtCoderの問題では、以下の画像のように問題ごとに配点が表示される。 f:id:task4233:20181203232134p:plain

配点とは厄介なもので、問題を解く前からその問題の難しさを無意識のうちに決めてしまうのだ。
(RPGで巨大な敵を見ると、戦わずして「逃げる」を選択してしまいそうになるアレだ。)

それが原因で、精進*1が進んでいなかったそんな夏休み。ある日Twitterを眺めていると、こんなツイートが流れてきた。

その内容は、配点の部分の要素を"???"で置換するというもの。「これだ!」と思い、本人にコンタクトを取りアイデアを使わせていただいた訳だ。

こうした流れで、
AtCoderのページで表示される配点を"???"で置換するスクリプト
を作ることを目的として開発を始めた。

どうやって作るか - 計画編

さて、アイデアの許可を得て、作るものの方針も立った。

しかし、ここで1つの問題に突き当たる。

「これ、どうやって作るんだ?」

その当時、私はUserscriptなんて触ったこともなかった。

なぜアイデアの許諾など乞うてしまったのだ、アホか?

と思いつつも、埒が明かないので既存のUserscriptを観察して構造を理解することにした。

そこで参考にしたコードのうち、印象に残ったのは以下の3つのコードだ。

1. AtCoderVirtualContestNowColorize

Author: Kaito Tateyama

スクリプト作者による説明
AtCoder Virtual Contestで現在開催されているコンテストに色付けをします。

コードがシンプルで私のUserScriptの基本形になった。初心者にとってシンプルなコードはありがたい。

2. AtCoderPerformanceColorizer

Author: satanic0258

スクリプト作者による説明
AtCoder(betaサイト)のコンテスト成績表におけるパフォーマンス値・レート値に色付けを行います.

コード中の perfColorizeMode , rateColorizeMode を指定することにより色づけ方法を変更することができます.

色付けモードをユーザ自身が編集できるという発想が印象的だった。Userscriptはユーザがダウンロードして使用するので、ユーザがコードを編集するというのは盲点だった。

3. ac-score-table-ja

Author: rsk0315

スクリプト作者による説明
AtCoder(beta.atcoder.jp)の日本語版で配点表を表示します.

この方のコードはコメントが本当に面白かった。

例えば、

// この要素が確実に目的のものを指しているかがアレなのですが,まぁ大丈夫でしょう var flag = $($('.dropdown-toggle')[0]).children()[0].src;

や、

// XSS に対してアなはずなんですが,AtCoder 社を信じるみたいなことをします

などがある。いわゆるコメント芸というやつだ。

この方はAtCoderScoresの作者でもあるので、そちらのソースコードも覗いてみると面白いと思う*2

他にも参考にしたコードはあるが、面倒なのでこの辺でやめておく。



さて、話を戻そう。

ソースコードの観察の結果、表示されるWebページのHTMLを取得して(1)要素を書き換えたり追加したり(2)するという手法を取っていた。この仕組みが分かったらこっちのものなので、以下のようなコーディングの手順を立てた。

  1. 配点を隠したい部分の要素の取得
  2. 任意の隠したい部分に対する、要素の上書き

以上。手順を書くほどもない単純な作りだ。

これだけで少し満足した私は、この手順に従ってコーディングをすることにした。

どうやって作るか - コーディング編

さて、手順が立ってしまえば競プロerの私にとってこれは唯の問題に過ぎない。

今回の私のスクリプトは短めなので、計画していた時の方がよっぽど大変だった気がする。

1. 配点を隠したい部分の要素の取得

先ほどのコードを見るとJavaScriptjQueryの2択だったが、jQueryは詳しくなかったので素のJavaScriptで書くことにした。編集したい部分の要素はdocument.querySelectorAll()で取得できるので、やるだけだった。

2. 任意の隠したい部分に対する要素の上書き

要素は取得出来たし、文字列を代入するだけ。

ということで以下のようなコードを打ち込み、謎の自信を身に纏いつつEnterキーを「ッターン」と叩いた。

{取得した要素}.innerText = {上書き用文字列};

f:id:task4233:20181204102811p:plain

変化していない。

Chromeデベロッパーツールで確認すると、取得していた要素はnodeListで、大きさが2のListだったようだ。

{取得した要素}[0].innerText = {上書き用文字列};

として解決した。何のためにわざわざ同じ要素を2つも用意しているんだろうか。

f:id:task4233:20181204234001p:plain

何はともあれ、原因はわかったのでnodeListの扱いに注意しつつ、残りの実装を終わらせた。

公開にあたって

ひとまず、目的としていた部分の目標は達成できたので、書いたUserscriptを公開することにした。

1. 公開するまでにやったこと

公開にあたり、以下の3つのことに注意した。

  1. 可能な限り簡潔にかくこと
  2. 見て意味を理解しやすい変数名を用いること
  3. 理解の助けとなるコメントを書くこと

これらは、リーダブルコードに書かれていた内容を参考にした。

結果的にversion1.0.0のコードはかなり読みやすいコードになっていると自負している(まぁコード量が少ないだけなのですが)。

何はともあれ、コード自体は完成したので、公開することになった。

しかし、公開するにあたり、スクリプト名やバージョン等を書く必要があったので、とりあえず埋めて無事完成した。

スクリプト名は、AtCoderScoreHinder。この名前は数時間で変更される訳だが、その話はまた後で。

2. 公開してからやったこと

さて、公開してから以下のようなコメントが来た。

おっしゃる通りです。凄く恥ずかしかった。ホントに。そして、指摘されてから10分程度でリネームした。

そして改善へ

このスクリプトを使用していて、色々と気になったので、以下の2つの改良を加えた。

1. リファクタリング

lambda式を用いることで、可読性のコードに書き換えた。

具体的には、関数名をhasError()isPrivatePage等に書き換える等である。

また、コメントや宣言等をまとめることで、修正しやすいコードにした。

2. ユーザによる編集機能

このUserscriptは初期設定で配点を"???"に置換する。しかし、他の文字列に変えたい人がいるかもしれないと考え、置換文字列を変更できるようにした。

その他、置換するページをbool型の変数で定義することで、置換するか否かをユーザが編集できるようにした。

具体的には以下のような部分だ。

// ---------------------------------------------------------------------------------------------
// 変更したい場合はここをいじってください
var display_score = '???';//            点数の代わりに置換される文字列
var display_last_status = true;//     過去の提出コードの点数を表示するか否か
var problem_page = true;//           問題ページで表示するか否か
var top_page = true;//                  トップページで表示するか否か
var source_code_page = true;//    ソースコードページで表示するか否か
var submitted_list_page = true;//   提出コード一覧ページで表示するか否か
// ---------------------------------------------------------------------------------------------

今回の制作で感じたこと

制作をする上で感じたことは、計画を立てることが大変だったということだ。

競プロでは、そもそも問題が与えられるのでContestants*3はコードの実装方針とコーディング部分のみを意識すれば良い。

しかし、今回のスクリプトに関してはそうはいかなかった。なぜなら、問題が与えられないからだ。更に言えば、問題を決めるのは自分自身でどのように作るかというのも自分に委ねられる。そのため、ここが難しい部分で時間も要した。

ただし、逆に言えば計画が立ってしまえば残りの作業は楽だということだ(精神的に楽ということでコーディングが楽ということではない)。実際に、今回は計画を立てた結果、かなり楽にコードを実装することが出来た(単純に仕様が簡単だったのも理由の1つだが)。

何らかの開発をする時にこの事に気づかず、行き当たりばったりのコードを書いて詰むというパターンも無くはないと思う。そのため、今後は何をどのような順番で実装するのかを決めてからコーディングをするのも手かなと感じた。

おわりに

歪な構成で読みにくかった部分もある中、この記事を最後まで読んでくれたことに感謝する。何も考えずにこの記事の執筆を決めた訳だが、なんとかなったので良かった(真似しないでください)。

制作物を作り上げることは大きな達成感があって良いので、また何か作る機会があれば作ってみたい。

*1:過去問を沢山解くこと

*2:サービス自体は素晴らしいもので、いつも使わせてもらっている

*3:問題を解く人のこと