0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CookieClickerのチートプログラム

Last updated at Posted at 2024-01-02

あらまし

完全に正月の自由研究で、CookieClickerのチートプログラムを書いた。

経緯

1000回転生する隠し実績をちまちま進めていたのだけど飽きがきたし、せっかくなので自動化で遊んでみることにした。元々はSeleniumに挑戦しようと思っていたのだけど、Macへの導入がなかなか大変そう・・Automatorでやろうかな?と色々考えたが
GoogleChrome拡張機能の、
ScriptAutoRunner
のJavaScriptで素朴にやることにした。
コンソールのメッセージは気にしない。

そもそも

数値操作をすれば転生実績は解除できたりしそうだけど、あくまで「ブラウザ操作の自動化」をやりたいというのが当初の目的だったので、謎に地道な操作を自動化しているかもしれない。チートのことをよくわかっていない。

コード

//フラグ
let statsBtnObsFlg = false;
// 変更を監視するノード
const $statsBtnObsArea = document.getElementById('comments');
// オプション
const statsBtnConfig = { attributes: true, childList: true, subtree: true };
// コールバック
const statsBtnCallback = function (mutationsList, observer) {
  for (const mutation of mutationsList) {
    for(let node of mutation.addedNodes) {
      // 要素のみを追跡し、他のノード(例 テキストノード)はスキップ
      if (!(node instanceof HTMLElement)) continue;

      if(node.getAttribute('id') === 'statsButton' && node.children[0].innerText === '記録' && !statsBtnObsFlg){
        //実績を表示・残り回数を取得
        const $statsBtn = document.getElementById('statsButton');
        $statsBtn.click();
        statsBtnObsFlg = true;
        observer.disconnect();
      }
    }
  }
};
//関数呼び出し
observeElement(statsBtnCallback,$statsBtnObsArea,statsBtnConfig);

//フラグ
let statsGeneralObsFlg = false;
// 変更を監視するノード
const $statsGeneralObsArea = document.getElementById('menu');
// オプション
const statsGeneralConfig = { attributes: true, childList: true, subtree: true };
// コールバック
const statsGeneralCallback = function (mutationsList, observer) {
  for (const mutation of mutationsList) {
    for(let node of mutation.addedNodes) {
      // 要素のみを追跡し、他のノード(例 テキストノード)はスキップ
      if (!(node instanceof HTMLElement)) continue;

      if(node.classList.contains('subsection') && node.querySelector('#statsGeneral') && !statsGeneralObsFlg ){

        //監視を停止
        observer.disconnect();
        statsGeneralObsFlg = true;

        //必要転生回数を取得
        const $reca = document.getElementById('statsGeneral').children[4];
        const recaCount = $reca.innerText.replace(/^.+昇天 (.+)回$/,'$1');
        const recaRemainsCount = 1000 - recaCount;

        //メインプログラム
        async function clickForReincarnation(){

          //通知を削除
          const $notes = document.getElementById('notes').children ? document.getElementById('notes').children : undefined ; 
          if($notes){
            const $notesBtn = $notes[$notes.length - 1];
            $notesBtn.click();
          } 

          //100購入タブに切り替え
          const $storeBulkTab = document.getElementById('storeBulk100');
          $storeBulkTab.click();

          //アップグレードの一括購入ボタンをクリック
          await clickUpgrade(2000,4);

          //すべての施設をループして購入不可能施設の一つ手前の施設の購入ボタンをクリック
          await clickProduct(4000,4);

          //アップグレードの一括購入ボタンを再度クリック
          await clickUpgrade(500,5);

          //天国に行って戻ってくる
          await goToHeaven();

          //アップグレード購入関数
          function clickUpgrade(d,c){
            return new Promise(resolve => {
              let counter = 1;
              let timerID = null;

              const proc = () => {
                if(counter <= c){
                  const $storeAllBtn = document.getElementById('storeBuyAllButton');
                  $storeAllBtn.click();
                  
                  console.log('upgrade',d,c,counter,timerID);
                  
                  //最後のループでループを止める
                  if(counter === c){
                    clearTimeout(timerID);  
                    resolve();
                  }
                  else{
                    timerID = setTimeout(() => { proc(); },d);
                  }
                }
                counter ++;
              }
              proc();
            });
          }    

          //施設購入関数
          function clickProduct(d,c){
            return new Promise(resolve => {
              let counter = 1;
              let timerID = null;

              const proc = () => {
                if(counter <= c){
                  //購入可能な施設に対してループ
                  const $products = document.querySelectorAll('.product');
                  const $enableprods = Array.from($products).filter(el =>
                    !el.classList.contains('locked') && !el.classList.contains('disabled')
                  );
                  $enableprods[$enableprods.length - 1].click();
                  
                  console.log('product',d,c,counter,timerID);
                  
                  //最後のループでループを止める
                  if(counter === c){
                    clearTimeout(timerID);  
                    resolve();
                  }
                  else{
                    timerID = setTimeout(() => { proc(); },d);
                  }
                }
                counter ++;
              }
              proc();
            });
          }

          //天国での手続き
          function goToHeaven(){
            return new Promise(resolve => {

              const proc = () => {

                //フラグ
                let ascendBtnObsFlg = false;
                // 変更を監視するノード
                const $ascendBtnObsArea = document.getElementById('wrapper');
                // オプション
                const ascendBtnConfig = { attributes: true, childList: true, subtree: true };
                // コールバック
                const ascendBtnCallback = function (mutationsList, observer) {
                  for (const mutation of mutationsList) {
                    if(mutation.type === 'attributes' && mutation.attributeName === 'class' && mutation.target.classList.contains('ascending') && !ascendBtnObsFlg){
                      //再転生ボタンをクリック
                      const $ascendBtn = document.getElementById('ascendButton');
                      $ascendBtn.click();

                      //ダイヤログをクリック
                      const $ascendDialogBtn = document.getElementById('promptOption0');
                      $ascendDialogBtn.click();

                      resolve();
                      ascendBtnObsFlg = true;
                      observer.disconnect();
                    }
                  }
                };
                //再転生ボタンのエリアを監視
                observeElement(ascendBtnCallback,$ascendBtnObsArea,ascendBtnConfig);

                //転生ボタンをクリック
                const $legacyBtn = document.getElementById('legacyButton');
                $legacyBtn.click();
                
                //ダイヤログをクリック
                const $legacyDialogBtn = document.getElementById('promptContentAscend').querySelector('.smallFancyButton');
                $legacyDialogBtn.click();
                
              }
              proc();
            });
          }
        }

        // 輪廻転生関数
        async function loopReincarnations(n) {
          for (let i = 0; i < n; i++) {
            await clickForReincarnation();
          }
        }

        // 規定の回数を指定して実行
        loopReincarnations(recaRemainsCount);
      }
    }
  }
};
//監視関数呼び出し
observeElement(statsGeneralCallback,$statsGeneralObsArea,statsGeneralConfig);

//監視関数
function observeElement(cb,t,c){
  // コールバック関数に結びつけられたオブザーバーのインスタンスを生成
  const observer = new MutationObserver(cb);

  // 対象ノードの設定された変更の監視を開始
  observer.observe(t, c);
}

感想

  • MutationObserverとasync/awaitの良い復習になった。
  • エラー発生時の対処記述がないのは微妙。でも今回はもうつかれた。これでよしとする。
  • 監視関数も都度停止してるけどサーバーにめちゃくちゃ負担かかってたらどうしよう。大丈夫と思うけど
  • 次はSpeedBakingⅢのチートコードを書きたいな〜。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?