QuizKnockの「人生最高得点を取る旅」にCookie Clickerで挑む

# はじめに
Cookie Clickerはコードを改変して遊ぶことが認められていて、大きい数字を扱うゲームです。そこで「人生最高得点を取る旅」に応用させるといい成績を狙うことができるかもしれないと思い挑戦してみました。

※Cookie Clickerを使った具体的な高得点の取り方を先に知りたい方は[実践](#実践)へ
※とにかく何点取れたか結果を先に知りたい方は[まとめ](#まとめ)へ

# Cookie Clickerとは
クッキーを増やすことが目的のブラウザゲームです。最初は1Clickで1つクッキーを増やすことしかできないですが、ゲームを進めていくうちに自動で大量のクッキーを増やせたり、1Clickでより多くのクッキーを増やすことができるようになっていきます。そしてうまく進むと京とか垓とか、それどころかもっともっと多くクッキーを増やすことができます。javascriptで書かれたコードを改変して遊ぶことが認められていることも特徴です。
![FireShot Capture 002 - 0クッキー - Cookie Clicker - orteil.dashnet.org.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3071987/5f8676ed-2b4a-922b-0f7c-7ae47b8d300e.png)
Cookie Clickerのリンクは[こちら](https://orteil.dashnet.org/cookieclicker/)

# 人生最高得点を取る旅とは
クイズを主に知識を広めるWebメディア「QuizKnock」がYoutube上で行った企画です。QuizKnockの中でもかなりの人気を誇る動画の1つです。決められた期間内にどれだけ高い点を取ることができるかを競います。以下のルールに則っていれば点を取る手段は何でもOK。

::: note
- 単位が「点」あるいは「ポイント」のものであること。
- 何回かの挑戦を合計したものはNG。1回の挑戦で取れたものであること。
    - ただし、1発勝負ではないため同じ手段を繰り返してその中での最高得点を採用しても良い。
- 点を取る手段が公のものとなっていること。
    - 例えば配点がとても高い自分専用のテストを解くなどはNG。
    - ※ネタバレになるので詳しくは控えますが、最初はこのルールがありませんでした。
:::

本家動画

https://www.youtube.com/watch?v=AISo8I4xYL4&t=1s&ab_channel=QuizKnock

今回はCookie Clickerを使って「人生最高得点を取る旅」でできるだけ高得点を取ることを目指していきます。

※本家動画のネタバレは極力控えますが、記事を全て読むと動画の展開が予想できてしまう可能性があります。前知識ゼロで動画を楽しみたい方は先に動画を観ることをおすすめします。

# 環境
- 使用ブラウザ・バージョン
Google Chrome 108.0.5359.125（Official Build） （64 ビット）
- Cookie Clickerのバージョン
v.2.048


# 考察
増やしたクッキーの枚数はとても大きい数字になっていくため、これをうまく利用すれば高得点が狙えそうに思えます。
![FireShot Capture 004 - 0クッキー - Cookie Clicker - orteil.dashnet.org.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3071987/413b6b46-b84f-f941-8111-fdaefa1d2984.png)

しかし、以下の点がネックになってきそうです。

::: note
1. Cookie Clickerは、単位が「点」ではなく「クッキー」であること。
1. Cookie Clickerは積み重ねによってクッキー(点)を増やすゲームであること。つまり、1回の挑戦ではない。
:::

そこでこれら解決するために以下のアイディアを出しました。

::: note
1. ゲーム画面の「クッキー」という表記を「点」という表記にするようコードを改変することで解決させる。
1. 「1回の挑戦で取る点数」を「1回のClickで得られる点数(クッキー)」とする。そして「1回のClickで得られる点数(クッキー)」の値をできるだけ大きくするようにコードを改変することで解決させる。
:::

もし、これら2つのアイディアを実現させることができれば以下の流れで高得点を取ることができそうです。

::: note
1. Cookie Clikerを新規で始める。
1. ゲーム画面の「クッキー」という表記を「点」に変更するようコードを改変。
1. 「1回のClickで得られる点」をできるだけ大きな値に変更するようコードを改変。
1. 1回Clickして高得点を取る
:::

ということで「表記変更」と「Clickで得られる点数を大きくする」2つのアイディアについて実現する手段を考えていきます。

### ゲーム画面の「クッキー」という表記を「点」に変更するようコードを改変。
「点」に改変できる方法がないか、コードを読んでみます。するとmain.jsの
```.js
Game.Draw
```
が格納している関数に「クッキー」と表記している実装がありました。
具体的には15730行目の
```.js
str=loc("%1 cookie",{n:Math.round(Game.cookiesd),b:str});
```
が「クッキー」と表記している実装になります。
多言語対応のためか、locという関数を用いていたりしてます。
strがクッキーの数を表していたため、もし上記のコードが
```.js
str=str + "点";
```
であれば、「点」という表記になりそうです。
そこで、Game.Drawに格納されている関数から表記を「点」に変更した関数を作っておきます。
<details><summary>表記を「点」に変更した関数(長いので折りたたみます)</summary>

```表記を「点」に変更した関数.js
function()
	{
		Game.DrawBackground();Timer.track('end of background');
		
		if (!Game.OnAscend)
		{
			
			var str=Beautify(Math.round(Game.cookiesd));
			if (Game.cookiesd>=1000000)//dirty padding
			{
				var spacePos=str.indexOf(' ');
				var dotPos=str.indexOf('.');
				var add='';
				if (spacePos!=-1)
				{
					if (dotPos==-1) add+='.000';
					else
					{
						if (spacePos-dotPos==2) add+='00';
						if (spacePos-dotPos==3) add+='0';
					}
				}
				str=[str.slice(0,spacePos),add,str.slice(spacePos)].join('');
			}
			
			//下記のコードをコメントアウト
			//str=loc("%1 cookie",{n:Math.round(Game.cookiesd),b:str});
			//ここまで
			
			//下記のコードを記述
			str=str + "点";
			//ここまで
			if (str.length>14) str=str.replace(' ','<br>');
			
			if (Game.prefs.monospace) str='<span class="monospace">'+str+'</span>';
			str=str+'<div id="cookiesPerSecond"'+(Game.cpsSucked>0?' class="wrinkled"':'')+'>'+loc("per second:")+' '+Beautify(Game.cookiesPs*(1-Game.cpsSucked),1)+'</div>';
			l('cookies').innerHTML=str;
			Timer.track('cookie amount');
			
			for (var i in Game.Objects)
			{
				var me=Game.Objects[i];
				if (me.onMinigame && me.minigame.draw && !me.muted && !Game.onMenu) me.minigame.draw();
			}
			Timer.track('draw minigames');
			
			if (Game.drawT%5==0)
			{
				//if (Game.prefs.monospace) {l('cookies').className='title monospace';} else {l('cookies').className='title';}
				var lastLocked=0;
				for (var i in Game.Objects)
				{
					var me=Game.Objects[i];
					
					//make products full-opacity if we can buy them
					var classes='product';
					var price=me.bulkPrice;
					if (Game.cookiesEarned>=me.basePrice || me.bought>0) {classes+=' unlocked';lastLocked=0;me.locked=0;} else {classes+=' locked';lastLocked++;me.locked=1;}
					if ((Game.buyMode==1 && Game.cookies>=price) || (Game.buyMode==-1 && me.amount>0)) classes+=' enabled'; else classes+=' disabled';
					if (lastLocked>2) classes+=' toggledOff';
					me.l.className=classes;
					//if (me.id>0) {l('productName'+me.id).innerHTML=Beautify(me.storedTotalCps/Game.ObjectsById[me.id-1].storedTotalCps,2);}
				}
				
				//make upgrades full-opacity if we can buy them
				var lastPrice=0;
				for (var i in Game.UpgradesInStore)
				{
					var me=Game.UpgradesInStore[i];
					if (!me.bought)
					{
						var price=me.getPrice();
						var canBuy=me.canBuy();//(Game.cookies>=price);
						var enabled=(l('upgrade'+i).className.indexOf('enabled')>-1);
						if ((canBuy && !enabled) || (!canBuy && enabled)) Game.upgradesToRebuild=1;
						if (price<lastPrice) Game.storeToRefresh=1;//is this upgrade less expensive than the previous one? trigger a refresh to sort it again
						lastPrice=price;
					}
					if (me.timerDisplay)
					{
						var T=me.timerDisplay();
						if (T!=-1)
						{
							if (!l('upgradePieTimer'+i)) l('upgrade'+i).innerHTML=l('upgrade'+i).innerHTML+'<div class="pieTimer" id="upgradePieTimer'+i+'"></div>';
							T=(T*144)%144;
							l('upgradePieTimer'+i).style.backgroundPosition=(-Math.floor(T%18))*48+'px '+(-Math.floor(T/18))*48+'px';
						}
					}
					
					//if (me.canBuy()) l('upgrade'+i).className='crate upgrade enabled'; else l('upgrade'+i).className='crate upgrade disabled';
				}
			}
			Timer.track('store');
			
			if (Game.PARTY)//i was bored and felt like messing with CSS
			{
				var pulse=Math.pow((Game.T%10)/10,0.5);
				Game.l.style.filter='hue-rotate('+((Game.T*5)%360)+'deg) brightness('+(150-50*pulse)+'%)';
				Game.l.style.webkitFilter='hue-rotate('+((Game.T*5)%360)+'deg) brightness('+(150-50*pulse)+'%)';
				Game.l.style.transform='scale('+(1.02-0.02*pulse)+','+(1.02-0.02*pulse)+') rotate('+(Math.sin(Game.T*0.5)*0.5)+'deg)';
				Game.wrapper.style.overflowX='hidden';
				Game.wrapper.style.overflowY='hidden';
			}
			
			Timer.clean();
			if (Game.prefs.animate && ((Game.prefs.fancy && Game.drawT%1==0) || (!Game.prefs.fancy && Game.drawT%10==0)) && Game.AscendTimer==0 && Game.onMenu=='') Game.DrawBuildings();Timer.track('buildings');
			
			Game.textParticlesUpdate();Timer.track('text particles');
		}
		
		Game.NotesDraw();Timer.track('notes');
		
		Game.runModHook('draw');
		
		Game.drawT++;
		//if (Game.prefs.altDraw) requestAnimationFrame(Game.Draw);
	}
```
</details>
あとはこの関数をGame.Drawに格納し直せば表記を「点」にできそうです。

### 「1回のClickで得られる点」をできるだけ大きな値に変更するようコードを改変。
これについてはCookie Clickerに関する記事を読むと見つかりそうな気もします。
ですがコードを読んで調べてみることにします。
するとmain.jsの
```.js
Game.computedMouseCps
```
が1回のClickで増やすクッキーの数を格納しているようでしたので、ここにできるだけ大きな値を代入すれば良さそうです。

# 実践
では、考察で出した手順をもとに高得点を取っていきます。
### 1. Cookie Clikerを新規で始める。
![FireShot Capture 013 - 0クッキー - Cookie Clicker - orteil.dashnet.org.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3071987/ca5895a8-a19f-0b5d-0f9e-273229d4cdd9.png)

### 2. ゲーム画面の「クッキー」という表記を「点」に変更するようコードを改変。
考察で作成した「点」に表記変更する関数をGame.Drawに格納します。
実際には以下のコードをブラウザのコンソールから実行すればOKです。
<details><summary>表記を「点」に変更する(長いので折りたたみます)</summary>

```表記を「点」に変更する.js
Game.Draw=function()
	{
		Game.DrawBackground();Timer.track('end of background');
		
		if (!Game.OnAscend)
		{
			
			var str=Beautify(Math.round(Game.cookiesd));
			if (Game.cookiesd>=1000000)//dirty padding
			{
				var spacePos=str.indexOf(' ');
				var dotPos=str.indexOf('.');
				var add='';
				if (spacePos!=-1)
				{
					if (dotPos==-1) add+='.000';
					else
					{
						if (spacePos-dotPos==2) add+='00';
						if (spacePos-dotPos==3) add+='0';
					}
				}
				str=[str.slice(0,spacePos),add,str.slice(spacePos)].join('');
			}
			
			//下記のコードをコメントアウト
			//str=loc("%1 cookie",{n:Math.round(Game.cookiesd),b:str});
			//ここまで
			
			//下記のコードを記述
			str=str + "点";
			//ここまで
			if (str.length>14) str=str.replace(' ','<br>');
			
			if (Game.prefs.monospace) str='<span class="monospace">'+str+'</span>';
			str=str+'<div id="cookiesPerSecond"'+(Game.cpsSucked>0?' class="wrinkled"':'')+'>'+loc("per second:")+' '+Beautify(Game.cookiesPs*(1-Game.cpsSucked),1)+'</div>';
			l('cookies').innerHTML=str;
			Timer.track('cookie amount');
			
			for (var i in Game.Objects)
			{
				var me=Game.Objects[i];
				if (me.onMinigame && me.minigame.draw && !me.muted && !Game.onMenu) me.minigame.draw();
			}
			Timer.track('draw minigames');
			
			if (Game.drawT%5==0)
			{
				//if (Game.prefs.monospace) {l('cookies').className='title monospace';} else {l('cookies').className='title';}
				var lastLocked=0;
				for (var i in Game.Objects)
				{
					var me=Game.Objects[i];
					
					//make products full-opacity if we can buy them
					var classes='product';
					var price=me.bulkPrice;
					if (Game.cookiesEarned>=me.basePrice || me.bought>0) {classes+=' unlocked';lastLocked=0;me.locked=0;} else {classes+=' locked';lastLocked++;me.locked=1;}
					if ((Game.buyMode==1 && Game.cookies>=price) || (Game.buyMode==-1 && me.amount>0)) classes+=' enabled'; else classes+=' disabled';
					if (lastLocked>2) classes+=' toggledOff';
					me.l.className=classes;
					//if (me.id>0) {l('productName'+me.id).innerHTML=Beautify(me.storedTotalCps/Game.ObjectsById[me.id-1].storedTotalCps,2);}
				}
				
				//make upgrades full-opacity if we can buy them
				var lastPrice=0;
				for (var i in Game.UpgradesInStore)
				{
					var me=Game.UpgradesInStore[i];
					if (!me.bought)
					{
						var price=me.getPrice();
						var canBuy=me.canBuy();//(Game.cookies>=price);
						var enabled=(l('upgrade'+i).className.indexOf('enabled')>-1);
						if ((canBuy && !enabled) || (!canBuy && enabled)) Game.upgradesToRebuild=1;
						if (price<lastPrice) Game.storeToRefresh=1;//is this upgrade less expensive than the previous one? trigger a refresh to sort it again
						lastPrice=price;
					}
					if (me.timerDisplay)
					{
						var T=me.timerDisplay();
						if (T!=-1)
						{
							if (!l('upgradePieTimer'+i)) l('upgrade'+i).innerHTML=l('upgrade'+i).innerHTML+'<div class="pieTimer" id="upgradePieTimer'+i+'"></div>';
							T=(T*144)%144;
							l('upgradePieTimer'+i).style.backgroundPosition=(-Math.floor(T%18))*48+'px '+(-Math.floor(T/18))*48+'px';
						}
					}
					
					//if (me.canBuy()) l('upgrade'+i).className='crate upgrade enabled'; else l('upgrade'+i).className='crate upgrade disabled';
				}
			}
			Timer.track('store');
			
			if (Game.PARTY)//i was bored and felt like messing with CSS
			{
				var pulse=Math.pow((Game.T%10)/10,0.5);
				Game.l.style.filter='hue-rotate('+((Game.T*5)%360)+'deg) brightness('+(150-50*pulse)+'%)';
				Game.l.style.webkitFilter='hue-rotate('+((Game.T*5)%360)+'deg) brightness('+(150-50*pulse)+'%)';
				Game.l.style.transform='scale('+(1.02-0.02*pulse)+','+(1.02-0.02*pulse)+') rotate('+(Math.sin(Game.T*0.5)*0.5)+'deg)';
				Game.wrapper.style.overflowX='hidden';
				Game.wrapper.style.overflowY='hidden';
			}
			
			Timer.clean();
			if (Game.prefs.animate && ((Game.prefs.fancy && Game.drawT%1==0) || (!Game.prefs.fancy && Game.drawT%10==0)) && Game.AscendTimer==0 && Game.onMenu=='') Game.DrawBuildings();Timer.track('buildings');
			
			Game.textParticlesUpdate();Timer.track('text particles');
		}
		
		Game.NotesDraw();Timer.track('notes');
		
		Game.runModHook('draw');
		
		Game.drawT++;
		//if (Game.prefs.altDraw) requestAnimationFrame(Game.Draw);
	}
```
</details>
コードの実行の仕方については以下の記事が参考になります。

https://www.javadrive.jp/javascript/console/index1.html

![FireShot Capture 014 - 0クッキー - Cookie Clicker - orteil.dashnet.org.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3071987/21ff9d35-8339-0b5a-f0d5-1096c699e70c.png)
表記が「点」になりましたね。

### 3. 「1回のClickで得られる点」をできるだけ大きな値に変更するようコードを改変。
今度はGame.computedMouseCpsにできるだけ大きい値を代入します。
実際には以下のコードをブラウザのコンソールから実行すればOKです。
```.js
Game.computedMouseCps = 1e+100;
```
ここではできるだけ大きい値ということで1e+100(10の100乗)としました。

![FireShot Capture 015 - 0クッキー - Cookie Clicker - orteil.dashnet.org.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3071987/fe07177f-af23-89cb-4d03-be4ff88207d4.png)

内部の値は変わりましたが、まだClickしてないので見た目は変わりません。

### 4. 1回Clickして高得点を取る
では準備が整ったので1回Clickして高得点を取ります。

![FireShot Capture 016 - 1.00e+100クッキー - Cookie Clicker - orteil.dashnet.org.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3071987/5078e6d8-6d94-0424-46b9-e1c2c2b26850.png)

見事1回のClickで1e+100点を取ることができました！！
ちなみに、Game.computedMouseCpsに代入する値が大きすぎると1回のClickで「NaN」と表示され測定不能になるようです。
「NaN」と表示されない上限値は調べきれてないですが、1e+100点でも十分高得点が取れていると思います。

# まとめ
Cookie Clickerを使えば今回の方法で1e+100(10の100乗)点は取ることができました！
本家の点数と比較すると2位に入ることができて好成績ですね！
それでも1位には及ばないのですが・・・
もし高得点を取る旅に出ることがあれば、これで高得点を取りに行きます！

この記事を書いてる時に気づいたのですが、表記を「点」ではなく、「〇〇〇〇〇〇転点」とすれば、もしかしたら1位と並ぶこともできたかも・・・