CookieClikerをjsから操作してみる

久しぶりの更新です。

最近、Twitterの一部界隈でCookie Clickerというゲームが流行っているようです。
ゲームのパロディ、ヒロイン※の官能小説なども出るほど、日本で人気なようです。

そんなに面白いのかと思ってページを開いてみたら、
ただCookieを焼くだけのブラウザゲーでした。

わけがわからん…。
おもむろに左側に表示されているCookieを右クリックして「要素を検証」してみたところ、

<div id="bigCookie"

あ、これはいけるわ。
と思ってjsを書いてクリック連打を実装しましたが、
思ったよりもこのゲーム一筋縄では行かないことが分かり、ハマってしまいました。

ただCookieを増やすだけでなく、放っておくだけで実績全解除が出来るようなjs
を目指してみたいと思います。

※おばあさんです

対応ブラウザ

Chrome最新版、Safari最新版、Firefox最新版で動作確認しました。
おそらく、document.querySelector()が使えるブラウザなら大丈夫です。

普段使っているブラウザがChromeなので、記事ではChromeで説明をしていきます。
他ブラウザをお使いの方は、適宜読み替えて下さい。

自作のjsをページ内で実行するには

普段JavaScriptを書いている方ならご存知かと思いますが、
ブラウザのコンソールは、現在のページに対して任意のjsを実行することが出来ます。

まず、
Chromeのメニュー > 開発/管理 > JavaScriptコンソールをクリックします。
ショートカットは、⌘+Shift+jです。

試しに2つほどjsを流し込んでみます。

流し込んだjs

このページで定義されているグローバルな変数にもアクセスできるので、
CookieClickerのゲームを司るGameオブジェクトにもアクセス出来るし、書き換えられます

ただし、ゲームの設定値や挙動自体を変えてしまうのはチートやであって、
自動制御という名目から離れているので、
あくまで、ユーザ操作で出来る範囲内の行動しか取らない方針で行きます。

行動の最適化はかけていないので、にわかTASみたいな感じと思ってもらえればと思います。

制御に必要な要素を取得する

まず、ゲームの進行に必要な要素をjsから制御できるよう抑えていきます。

このゲームでは、jQueryが読み込まれていないらしく、$()が使えません。
jQueryもundefinedです。

なので、要素の取得は、お手軽にdocument.querySelectorAllを使っていきます。
CSSのセレクタを使って要素を取得できる組み込みメソッドです。

セレクタ:#bigCookie

画面左側にあるクリックするCookieです。
自動制御ならではの高速連打は、Cookie生産量に大きく影響します。

ビルディング

セレクタ:#products .product

ビルディングの一覧が取得できます。
この子らを買わないと実績も生産も何もないので必須です。

アップグレードアイテム

セレクタ:#upgrades .upgrade

ビルディングの強化や、クリック性能の強化等のアイテムが買えます。
これらを買っていくことでどんどんCookie生産効率が上がっていきます。
実績の解放にも必要です。

セレクタ:#goldenCookie

何分かに1度ほど出現し、クリックすると良いことが起こるCookieです。
生産効率の上昇や、実績の解放に関わるしデメリットが無いので抑えておきます。

ニュース

セレクタ:#commentsText

画面上部の中央に表示されているニュースです。
いくら自動制御といえど、このゲームの世界観は楽しみたいものです。
実績には関係ないのでこれは必須ではありません。

基本的な処理

自動制御するからには、1度実行したらあとは放置で居たいものです。 そして、ゲームの設定をうっかり書き換えてしまうのも怖いのでグローバルは使いたくありません。 さらに、毎回クエリをかけるわけにもいかないので、DOMはキャッシュしておきたいです。

ということで、即時関数setIntervalを使います。
リピートの間隔は、適当に設定して下さい。

!function() { // 長いので省略 var qs = function(selector) { document.querySelectorAll(selector); }; var FPS = 500, // 1秒で処理を実行する回数 upgrades = qs("#upgrades .upgrade"), products = qs("#products .product"), cookie = qs("#bigCookie"), golden = qs("#goldenCookie"); setInterval(function() { // ここに操作を追加 }, 1000 / FPS); }();

基本的な枠組みはこんな感じになると思います。

次に、jQueryが無いので、いくつか汎用関数を作っていきます。
jQueryをjsから読み込ませることも出来るのですが、大した処理もないのでやりません。

繰り返しを行うeach
指定した要素が特定のクラスを持っているか調べるhasClassを定義しました

var each = function(arr, fn) { for(var i = 0; i < arr.length; i++) { fn.call(arr[i], arr[i], i); } }, hasClass = function(el, className) { return el.classList.contains(className); };

こんな感じです。 これ以降の処理は、特に記述がない限りsetIntervalの中の関数に書いていきます。

まず、Cookieをクリックさせてみます。
クリックイベントを発生させるには、onclickメソッドが使えます。

ゴールデンCookieも同様に、出現してようとなかろうとひたすら連打です。

cookie.onclick(); golden.onclick();

これだけです。設定したFPS分だけCookieがクリックされます。 ゴールデンCookieは出現した瞬間にクリックされるので全く見えません。笑

ビルディング・アップグレードを買う

アイテムを買うのも同様にonclick()で買えます。
値段が足りようと足りなかろうとonclick()してしまって問題ないのですが、

個人的にあまり好みではありません。
ゴールデンCookie連打しておいて何を言ってるんだお前はという話ですが。笑 ということで先ほど定義したhasClassを利用します。

購入可能なアイテムにはenabledというクラスが付くので、
アイテムを順に見て行き、enabledクラスを持っていたら購入という処理にします。 値段が足りるものを手当たり次第買っていきます。

同様に、eachの第1引数をupgradesにするだけで、アップグレードも同様に買えます。

each(products, function(el) { if(hasClass(el, 'enabled')) { el.onclick(); } }); each(upgrades, function(el) { if(hasClass(el, 'enabled')) { el.onclick(); } });

ここはお好みで。

これでは詰まる

しかし、これですんなり行くなら苦労しません。

このゲーム、ビルディングの価格バランスがだいぶ極端なので、
買えるけど買わない処理を挟まないと、ビルディングの所持数に激しく偏りが出ます。 農場や工場など、買ってもしょうがないものは、必要最低限しか要りません

ビルディング所持数に関連した、実績解放のためには

  • 指:200個
  • おばあさん:128個
  • その他:100個

を所持する必要があります。
ということで、ビルディングの購入ロジックを少し変えます。

まず、購入する数を制御する配列をFPSの下あたりに定義しておきます。

// 全ビルディングを10個ずつ, 50個ずつ、と買い揃えていく var BUY_STEPS = [10, 50, 100, 128, Number.MAX_VALUE]; // 所持数を取得するメソッドを定義 var getOwnedCnt = function(el) { var cnt = el.childNodes[1].childNodes[2]; return cnt ? +cnt.textContent : 0; }

次に、eachの部分を書き換えます。

var nextStep = true; each(products, function(el) { var buyCnt = getOwnedCnt(el); if(hasClass(el, 'enabled') && buyCnt < BUY_STEPS[0]) { el.onclick(); } nextStep = nextStep && buyCnt >= BUY_STEPS[0]; }); if(nextStep) BUY_STEPS.shift();

アイテムを単に買い揃えていくことが最適な行動ではないのですが、
あまり細かいロジック考えるのも面倒なので、思考停止です。

これで、値段が足りていても既に必要分所持している場合には買わないことが可能になります。

少ない数であれば、高ランクのものも比較的容易に手に入るので、
タイムマシン、コンデンサあたりを早めに確保し、序盤の生産効率を上昇させています。

特殊な処理

これでしばらく運用していたのですが、自動制御の敵がまた現れました。

購入確認ダイアログ

ビンゴセンター・研究所を購入すると出るアイテムのいくつかが、 購入前に、「購入しますか?」という確認ダイアログが出ます。

確認ダイアログのwindow.confirmは、
ユーザの選択が[はい]ならtrue、[いいえ]ならfalseを返す実装になっています。

ダイアログを操作するより、関数の動作を変える方が楽なので、上書きします。

window.confirm = function(){ return true; };

これで、ダイアログは出ず、強制的に"はい"が選択されたことになります。
jsの処理を止めるのはこいつだけなので、ここさえ超えてしまえばあとは放って置くだけです。

実績:NeverClick

隠れ実績として「Never Click」という実績があります。
これは、「1度もCookieをクリックせずに100万Cookie生産する」ことが条件なのですが、

先ほどの記述だと、無条件にCookieを連打してしまいます。
およそ0.03秒で条件失敗です。

ただし、ゴールデンCookieのクリック数は条件に含まれないようです。

ということで、隠し称号を取るのであれば、
Cookie.onclick()の行をコメントアウトして下さい。 ゴールデンCookieとアイテム購入で稼いでいきます。

最後に

今回は重いきり無駄遣いをしていましたが、

「このjQueryセレクタで合ってる?」なんて時にコンソールでサクッと書くのが良かったりします。 普通に、jsのデバッグをする際にも使えるので、コンソールから入力するjs、使ってみて下さい。

最終的に、自分が実績全解放までに使ったjsは、以下のようになりました。
1週目のプレイではDONT_BUY_ELDERをfalseにしてあげて、
頃合いを見てリセットが必要になります。そこまでの自動化は、また後々ということで。