2020年10月27日
Webとキューブと私
~第3回 配列(JavaScript)の代入とコピー~
こんにちは、松原です。
今は「案件相談窓口」として、お客様からご相談いただいた案件の見積やスケジューリングに必要な作業工数の算出を担当しています。元コーダーで、たまにコーディングもします。
在宅ワーク期間にはじめたルービックキューブにド嵌りして、この度「自動でキューブの展開図を描画するプログラムを作ってみよう!」という連載企画をスタートしました。
第1回では、ルービックキューブ(スピードキューブ)をはじめたきっかけや、キューブの面を崩す「スクランブル」の方法。これから制作するプログラムの構想についてお話ししました。
※スクランブルとは:キューブを6面揃える前の準備として、面をバラバラに崩すこと。キューブには回転記号があり、その回転記号の順番どおりにキューブを回せば、指定された形にキューブをスクランブルできる
これから制作する「ランダムでスクランブルを表示して、自動でキューブの展開図を描画するプログラム」は、崩れたキューブをいかに早く揃えるかを競うスピードキューブを練習するときにも使えます。
第2回からHTMLとJavaScriptを使って制作をスタート。世界キューブ協会 (WCA)で公開されているデータベースから必要なスクランブルを抽出し、ランダム表示&更新ボタンを実装しました。
そろそろコーディングがわからない人には理解しにくいフェーズに入ってきましたが…自分の研究を続けるべく、進んでいきます。第3回となる今回は、JavaScriptの配列を使って展開図を表示させるための「仕組み」をプログラミングしていきたいと思います。
余談ですが、先日ルービックキューブのオンラインLT大会に参加しました。「LT」はLightning Talks(ライトニングトーク)の略で、発表者は5分程度の持ち時間でテーマに沿ったプレゼンテーションを行います。
発表者にWeb開発のエンジニアがいて、思いがけずWebの勉強になったり、スピードキューブの大会に約150回参加した人の海外遠征の話などが聞けて、想像以上に有意義な時間を過ごせました。
大会といえば、このご時世リアルで集まるのはなかなか難しいですが、お手軽なネットコンテストもあります。立体パズルの専門店tribox(トライボックス)が主催する「トリコン」は、メールアドレスを登録するだけで、いつでも誰でも参加できるスピードキューブのネットコンテストです。自分の好きなタイミングでタイムを計測して登録できるので、私も毎週チャレンジしています!
最大の難関:JavaScriptでキューブの回転を再現
さて本題に戻ります。
今回は「Scrambler(仮)」完成イメージのこの部分の「仕組み」を実装します。
更新ボタンをクリックするとスクランブル(回転記号)が表示されます(第2回で実装)。そのスクランブルのとおりにキューブを回したときの展開図がここに表示されます。
展開図を表示するためにどのパーツがどこに移動するのかを検証し、パーツの移動パターンをプログラミングします。さらに、回転記号の順番どおりにそのプログラム(命令)を実行させることで、展開図の元となるキューブ6面の状態を作り出します。
もし私が、ただの玩具としてキューブを回しているだけだったら、キューブの動きについてここまで深く考えることはなかったでしょう。しかし今は、この動きを完全に理解しなければ、Scrambler(仮)を完成させることはできません。逆に言うと、この仕組みさえ出来てしまえば完成したも同然です。
初期状態の設定
まずはキューブが6面揃っている状態で、全54パーツ(9マス×6面)の色情報をJavaScriptの配列に格納します。少し噛み砕いていうならば、白、緑、赤、青、黄、オレンジ、6色の服をそれぞれ9人ずつに着てもらい、その総勢54人の方々に、アパートの指定の部屋番号に入居してもらうイメージです。
そしてアパートの扉が入居した人の服の色になる…そんなイメージです。
そしてこの後、アパートの入居者はキューブが回転するたびにお引越ししていただきます。この「お引越し」が、キューブの各パーツの移動を表します。
実際のキューブを回して検証
ではどのように移動するのか、実際にキューブを回して検証してみましょう。
「R(アール)」の回転記号を例に解説します。
向かって右面(Right)を時計回りに90度回すのが「R」です。
キューブの全パーツに、配列のインデックス(アパートの部屋番号)を書いたシールを貼り付けて、それぞれのパーツがどのように動いたのかを確認します。我ながらかなりアナログな手法ですが、パーツがどのように移動しているかは一目瞭然です。
前面(Front)を見てみると、02、12、22の場所に元々下面(Down)にあった黄色のパーツが移動しています。上面(Up)、背面(Back)、下面(Down)も同じように見ていくと、それぞれのパーツが次のように移動していることがわかりました。
これが「R」の時の動きのパターンです。
たとえパーツの色が変わったとしても、この規則性は変わりません。このパターンどおりに、移動先のインデックスの値(アパートの入居者)を代入するプログラムを作れば、あとはそれをスクランブルの順番に実行するだけです。
白になるはずのパーツが、なぜか緑に。
ところが、先程の値を代入するプログラムを(A)~(D)の順に実行していくと、(D)の処理で次のような問題が発生しました。
(A)前面 → 上面 | |
---|---|
(B)下面 → 前面 | |
(C)背面 → 下面 | |
(D)上面 → 背面 |
上面は最初白だったのですが、(A)の処理で02、12、22が白から緑に変わっています。そのため(D)の処理で本来白になるはずの背面(Back)のパーツが緑になってしまいました。
プログラムは記述順に処理が走るのでABCDは時系列です。しかし、実物のキューブでは1回転で同時に発生している動きなので、そのプログラム(時系列)と実物(同時発生)の差が、この問題を引き起こしてしまったのです。
これでは全ての処理が完了する前に、一部の配列の値が代入後の値となってしまうため、キューブ6面の初期状態を一旦別の場所に退避しておく必要があることに気づきました。
コードで書くとこんな感じ。
See the Pen sample1.js by M Matsubara (@matsume) on CodePen.
事前にキューブ6面の初期状態を設定している配列「arrayF、arrayD、arrayB…」があります。その配列を(A)~(D)の処理を実行する前に、一旦別の変数「stashF、stashD、stashB…」にコピーして保管します。
よし、これでOK!
白になるはずのパーツが、なぜか緑に。PartⅡ
…と思ったら、だめでした。
なぜならば、JavaScriptの配列のコピーは、値を複製する「ディープコピー」ではなく、参照先を複製する「シャローコピー」なので、実際は「arrayF」も「stashF」も同じ値を参照する配列だったのです。つまり、保管したかったのは入居者(値)だったのですが、住所(参照)を複製して保管していたということです。
これは「JavaScriptあるある」なので、調べると解決方法はすぐ出てきました。
See the Pen sample2.js by M Matsubara (@matsume) on CodePen.
このように記述すれば、配列の値を保管しておくことができます。
これで今度こそOKでした!
See the Pen scramble.js by M Matsubara (@matsume) on CodePen.
今回のまとめ
今回も順調にトラップに引っかかりました。かなり初歩的なところなのでお気付きかもしれませんが、コーダー時代からJavaScriptは苦手で、正直避けてきたところがあります。しかし、大好きなルービックキューブがテーマだとこんなにも頑張れるのか!と自分でも驚いています。好きなことからこんな風に幅を広げることもできるんだなぁ、と実感しています。
今後の課題としては、ご覧いただいてもわかるようにとにかくコードが長いんです。
あまり動きのパターンの解析ができておらず、似たような記述が多くなっています。一度社内でコードレビューしてアドバイスが欲しい…なんて考えています。
ただいまのBest average of 5:34.89
そして私のスピードキューブ成長の記録ですが、前回から3秒近くタイムが縮みました!この前は0.48秒の更新だったので、大幅アップと言っていいのではないでしょうか。確かに最近は好タイムを安定して出せるようになってきた気がします。ただ歳のせいか、新しいことを覚えると前に覚えたことを忘れてしまうので(汗)キューブとあわせて記憶術の勉強もした方がいいのかなと思う今日この頃です。
※「average of 5」は、連続した5回のソルブ(6面を完成させる一連の動作)の中で、最も早いタイムと最も遅いタイムの2回を除いた、3回のタイムの平均値です。