マラソンコンテスト終了後のツイッターTLとかで、他参加者の自作ビジュアライザを見るたびに「次回は自分もやってみよう」と思うものの、どう手をつけたものかなとなって結局作らずに終わる、
みたいなことが繰り返されていたので対策として、敷居を下げるような手軽に作れる方法が何かないかと調べてみました。
その結果、2つほど良さそうなものを見つけたので、備忘録としてこの記事を作りました。
同じ悩みを抱えている人の参考にもなったら幸いです。
実際に自作もしてみていて、その際 AHC018 を題材にしています。
問題内容を把握している方が読みやすいとは思いますが、例示に使うだけなので、そうでなくてもさほど支障なく読めると思います。
概要
- 「公式Webビジュアライザを流用する方法」
- 「コンソールでビジュアライズする方法」
の2種類を紹介します。
それぞれ以下のような利点があると感じて選定しました。
「公式Webビジュアライザを流用する方法」は、タイトルの通りに公式Webビジュアライザの機能を流用できるので、自前で用意しなければならないものが少なくて済みます。例えば、入力フォームやイベントハンドラ、アニメーション機能が公式Webビジュアライザの方で用意されています。
「コンソールでビジュアライズする方法」は、コンソール上で完結しているため、HTML・CSSやCanvasといった GUI用の知識を新たに入れる必要が無く、コンテストで使う知識の延長だけで作れる点が利点だと感じています。
ここからは、それぞれの手法について詳しく見ていきます。
公式Webビジュアライザを流用する手法
tsukammoさんのAHC018参加記内で使用されていた手法です。
hackmd.io
真似て自作してみたものが、以下の画像になります。
元の公式Webビジュアライザがこちらで、これをダウンロードして改造して、本体の右横に白黒の図を付け足した形になります。
本体画像の補助情報として、掘削の深さをグレースケールで表示しています。
アニメーション機能にも乗っかっていて、以下のように動画表示もできます。
アニメーション機能にも割と簡単に乗っかれた。
— gobi (@gobi_503) 2023年3月18日
凄くいいなこれ。 pic.twitter.com/ZOF1uwXO27
この手法ではこのように、本体のビジュアライザを補強する形で情報を載せることができます。
この例では図形でやっていますが、そのターンの補助情報をテキストで表示する、くらいな使い方でも十分有用なのではないかなと思っています。
続けて、実装周りの話をしていきます。
ローカル起動手順
以下の手順で、公式Webビジュアライザをローカルで実行できます。
- 公式Webビジュアライザをダウンロード保存
1.
のHTMLから依存しているjs ファイルとwasmファイルをダウンロードして同一ディレクトリに突っ込む- 上述のディレクトリ内で
http-server
を実行して、ローカルHTTPサーバを立ち上げる- https://qiita.com/terufumi1122/items/39b2a3659bc585c07f64 を参考にしました。
http-server
はnpm
からインストールできます。
http://localhost:8080/Visualizer.html
にブラウザからアクセス
(ローカルファイルホスティングをしているのは、CORS 回避の都合です*1。)
ビジュアライザ改造の実装の説明
Canvas APIを主に使用して実装しています。
公式WebビジュアライザをダウンロードしたHTMLファイル(以降はVisualizer.html
と呼びます)に以下のタグを追記して canvas を設置。
<canvas id="canvas" width="400" height="400"></canvas>
下記のJSファイルをVisualizer.html
に読み込ませる。
const N = 200 const MAX_D = 5000 var canvas = document.getElementById('canvas') var context = canvas.getContext('2d') function callMyVis() { context.clearRect(0, 0, canvas.width, canvas.height) const turn = document.getElementById('turn').value const output = document.getElementById('output').value const rows = output .split('\n') .filter((row) => row[0] != '#') .map((row) => row.split(' ').map((sn) => Number(sn))) var tbl = new Array(N) for (let y = 0; y < N; y++) { tbl[y] = new Array(N).fill(0) } for (const [y, x, power] of rows.slice(0, turn)) { tbl[y][x] += power } for (let y = 0; y < N; y++) { for (let x = 0; x < N; x++) { const power = tbl[y][x] if (power == 0) { continue } context.fillStyle = grayScale(255 * ((1 - power / MAX_D) * 0.9)) context.fillRect(x * 2, y * 2, 2, 2) } } } function grayScale(n) { return `rgb(${n}, ${n}, ${n})` }
(JSコードは、ビジュアライザが指定しているターンの掘削状況を作成しキャンバスに描画、をしています。)
そして、Visualizer.html
内にあるvisualize()
関数の冒頭で、上記の callMyVis()
関数を呼べば完成です。
visualize()
関数はアニメーションターンが更新されるたびに実行されるので、これだけでアニメーション化できちゃいます。
気になっている点
本体のビジュアライズ画像自体の改造もしたかったのですが、参考記事内でも書かれているように、その部分はWebアセンブリで生成されていていじれませんでした。
(というよりその都合で、本体のビジュアライズ画像の補足情報を足す、という構成にしています。)
一方で、ローカル版ツール内の vis.rs
がWebアセンブリを吐くように作られているように見えるので、これを上手く組み込めるならなんとかできるのかもな、とかを思っていますが確かめてはいないです。
関連する良さそうな手法
iwashi31さんがAHC019後に出していた上記の記事で、ローカルに落としたWebビジュアライザを改造して、実行結果を自動で読み込むようにする手法 が紹介されていました。
問題に依存せず使えそうでかつ有用そうで、良さそうな手に感じました。
今後のコンテストでテンプレとして使っていこうと考えています。
コンソールでビジュアライズする方法
mooakiさんのこちらの記事を参考にしました。
AHC018を題材に自作してみたものが以下になります。コンソール上で実行されてます。
動画版 pic.twitter.com/egqLjaeUKJ
— gobi (@gobi_503) 2023年3月19日
こんな感じで、コンソール上でも思いの外*2リッチなアニメーションが作れます。
参考記事内の15パズルのビジュアライズも凄いので是非見てみてもらいたいです。
これは主にANSIエスケープシーケンスという、ターミナル出力の色付けや削除、カーソルの移動などを表現できる端末制御用のシグナル*3を使用して作成しています。
新たに覚えなければいけないのはこれくらいで、基本的には文字列を組み立てて出力するプログラムを作る、という作業になるので、使用知識はコンテストで使う範囲で概ね事足り、GUI向けの知識を新たに仕入れることなく制作できるという点がメリットとして大きいと感じています。
シーケンス自体もそんなに覚えることもなく、参考記事中の「プログラム中でよく使いそうなものは関数にしておくと便利です。」と書かれている箇所の下に例示されてる内容くらいで概ね事足りると思います。
あとは、コンソールに閉じて作業できる*4のも利点になりそうかなと思っています。
詳しい作り方は参考記事の方に書かれているので、それはそちらを読んでいただくことにして、 ここでは自分がやってみた上での所感やtipsを書こうと思います。
以降は、参考記事の内容を把握していることが前提の内容になります。
自作ビジュアライザの軽い説明
最初に盤面を記述し、その後ターンごとに掘削箇所にカーソル移動して、グレースケールで表現した掘削量を上書きする、ということをしています。
盤面の各セルは、空白文字に背景色を付与する形で表現しています。
これ自体に公式ビジュアライザ以上の表現力は無いのですが、今回は手法を試すこと自体が目的だったのでこれでよしとしています。
気になったところ
- AHC018の盤面サイズだと、差分更新にしないと速度的に少しキツかった*5。
表示スペースサイズ的にもちょっと辛さを感じたので、参考記事で扱っているAHC011のような、盤面サイズ小さめの問題向けの手法な印象ではありました。 - 文字が縦長なので、1セル1文字で作ると画面が縦長になってしまう。
- コンソールの文字フォーマットの設定でなんとかできそうな気もします。今回の自作例では、気にせずに縦長盤面で妥協しちゃってますが。
tips
背景色のカラーコードの取り扱い
自分の環境では、こちらに載っている「RGBでのカラーコードを指定」が背景色に対しては使えなかったので、「0~255のカラーコードを指定」する方法を利用しました。
これにあたってカラーコードの選定に悩んだので、
以下のような、カラーコードの番号とその色を表示するコードを用意しました。
for i in `seq 0 255` do printf "\e[48;5;%dm%d" $i $i printf "\e[0m " done; printf "\e[0K\n"
以下のような出力が得られます。
232~255 の連番のところがグレースケールになっているので、濃淡を表現したいところでの使い勝手が良さそうでした。実際、掘削の深さはこれで表現しています。
224,210,209,197,160 あたりで赤色の濃淡の表現もできました*6。岩盤の硬さはこれで表現しています。
終わりに
やはりというか、視覚的に結果を確認できるものができると達成感を得やすくて良いですね。眺めてニヤニヤしています。
私は元々、画像をパラパラ漫画的に表示するだけのビジュアライザ、くらいしか作ったことが無くて、ビジュアライザ作りに対しては苦手意識を持っていたのですが、今回調査した手法のおかげである程度のもの*7を作り切ることができました。
私と同じように苦手意識を持っている方は是非試してみて欲しいです。