久しぶりの更新です。
最近、Twitterの一部界隈でCookie Clickerというゲームが流行っているようです。
ゲームのパロディ、ヒロイン※の官能小説なども出るほど、日本で人気なようです。
そんなに面白いのかと思ってページを開いてみたら、
ただCookieを焼くだけのブラウザゲーでした。
わけがわからん…。
おもむろに左側に表示されているCookieを右クリックして「要素を検証」してみたところ、
<div id="bigCookie"…
あ、これはいけるわ。
と思ってjsを書いてクリック連打を実装しましたが、
思ったよりもこのゲーム一筋縄では行かないことが分かり、ハマってしまいました。
ただCookieを増やすだけでなく、放っておくだけで実績全解除が出来るようなjs
を目指してみたいと思います。
※おばあさんです
Chrome最新版、Safari最新版、Firefox最新版で動作確認しました。
おそらく、document.querySelector()
が使えるブラウザなら大丈夫です。
普段使っているブラウザがChromeなので、記事ではChromeで説明をしていきます。
他ブラウザをお使いの方は、適宜読み替えて下さい。
普段JavaScriptを書いている方ならご存知かと思いますが、
ブラウザのコンソールは、現在のページに対して任意のjsを実行することが出来ます。
まず、
Chromeのメニュー > 開発/管理 > JavaScriptコンソール
をクリックします。
ショートカットは、⌘+Shift+j
です。
試しに2つほど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(); } });
ここはお好みで。
しかし、これですんなり行くなら苦労しません。
このゲーム、ビルディングの価格バランスがだいぶ極端なので、
買えるけど買わない処理を挟まないと、ビルディングの所持数に激しく偏りが出ます。 農場や工場など、買ってもしょうがないものは、必要最低限しか要りません。
ビルディング所持数に関連した、実績解放のためには
を所持する必要があります。
ということで、ビルディングの購入ロジックを少し変えます。
まず、購入する数を制御する配列を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の処理を止めるのはこいつだけなので、ここさえ超えてしまえばあとは放って置くだけです。
隠れ実績として「Never Click」という実績があります。
これは、「1度もCookieをクリックせずに100万Cookie生産する」ことが条件なのですが、
先ほどの記述だと、無条件にCookieを連打してしまいます。
およそ0.03秒で条件失敗です。
ただし、ゴールデンCookieのクリック数は条件に含まれないようです。
ということで、隠し称号を取るのであれば、
Cookie.onclick()の行をコメントアウトして下さい。 ゴールデンCookieとアイテム購入で稼いでいきます。
今回は重いきり無駄遣いをしていましたが、
「このjQueryセレクタで合ってる?」なんて時にコンソールでサクッと書くのが良かったりします。 普通に、jsのデバッグをする際にも使えるので、コンソールから入力するjs、使ってみて下さい。
最終的に、自分が実績全解放までに使ったjsは、以下のようになりました。
1週目のプレイではDONT_BUY_ELDER
をfalseにしてあげて、
頃合いを見てリセットが必要になります。そこまでの自動化は、また後々ということで。