目的
研究に疲れきってしまってせっかくプログラミングできるのだからと、昔遊んでいたCookie Clickerというゲームを完全自動化を目指してみた。
ここでの「完全自動化」とは
こんなことができたら嬉しい
python CookieClicker.py
→ クッキークリッカーが起動
→ ファイルからセーブ読み込み
→ 大クッキー&ゴールデンクッキーの自動クリック
→ 適宜アップグレード&building購入
→ 適宜セーブ
そもそもCookie Clickerとは
ババアがクッキーを焼くゲームである。以上。
遊びたい人はこちらから
作ってみたもの
基本的には、python(selenium)でFirefoxをリモートで動かしつつ、javascriptをselenium越しに操作させる。
自動クリック部分のソースコードは、Cookie Cliker Wikiからお借りしました。
ファイル構成
Cookie Clicker/
├ Building.py
├ AutoCookieClicker.py
└ save(git)/
└ savedata
ソースコード
ガバ設計、動けばいいや程度のものなので色々とひどい状態です。
githubはこちら:https://github.com/RyuSA/CookieClicker
import sys
import git
import csv
import time
from selenium import webdriver
from time import sleep
from datetime import datetime
from buildings import Building
from configparser import SafeConfigParser
URL_CookieClicker = "http://orteil.dashnet.org/cookieclicker/"
EXECUTE_CLICK_BIGCOOKIE = "setInterval(function() {Game.ClickCookie(); Game.lastClick=0; }, 1000/1000);"
EXECUTE_CLICK_GOLDENCOOKIE = "setInterval(function(){for (var i in Game.shimmers) { Game.shimmers[i].pop(); }}, 500);"
def git_commit():
repository = git.Repo("save")
repository.git.add(".")
commit_meessage = datetime.now().isoformat()
repository.index.commit(commit_meessage)
class CookieCliker(object):
Path = "save/savedata"
WINDOW_WIDTH = 1200
WINDOW_HEIGHT = 500
SAVE_INTERVAL = 30
NUMBER_OF_BUY = 1000
INTERVAL = 30
PRODUCTS_LIST = ("Cursor", "Grandma", "Farm", "Mine", "Factory", "Bank", "Temple", "Tower", "Shipment", "Lab", "Portal", "Time_Machine", "Condefsor", "Prism", "Chansemaker")
def __init__(self, url):
self.driver = webdriver.Firefox()
self.driver.set_window_size(CookieCliker.WINDOW_WIDTH, CookieCliker.WINDOW_HEIGHT)
self.driver.get(url)
# 2秒待てば、ブラウザの読み込みが間に合う
sleep(2)
# クリック用の大クッキー取得
self.BigCookie = self.driver.find_element_by_id("bigCookie")
self.products = []
for id, name in enumerate(CookieCliker.PRODUCTS_LIST):
self.products.append(Building(name = name, id = id, driver = self.driver))
print("Cookie Clicker has been initialized")
# セーブデータをブラウザからコピぺして保存
def Export_Savedata(self):
OpenTextAreaScript = "Game.ExportSave();"
self.driver.execute_script(OpenTextAreaScript)
savedata = self.driver.find_element_by_id("textareaPrompt").text
f = open(CookieCliker.Path, 'w')
f.write(savedata)
f.close()
self.driver.execute_script("Game.ClosePrompt();")
git_commit()
def Import_Savedata(self):
OpenTextAreaScript = "Game.ImportSave();"
ImportScript = "Game.ImportSaveCode(l('textareaPrompt').value);"
LoadScript = "Game.ClosePrompt();"
# Importを開く
self.driver.execute_script(OpenTextAreaScript)
# 注意:savefileの中には1行しか記述されていないはず
f = open(CookieCliker.Path, 'r')
# savedataが読み込めないことがあったら、空のセーブデータでプレイ
savefile = ""
for a in f:
savefile = a
f.close()
self.driver.find_element_by_id("textareaPrompt").send_keys(savefile)
self.driver.execute_script(ImportScript)
self.driver.execute_script(LoadScript)
for p in self.products:
p.Update(self.driver)
print("/////////////////////////////////////")
print("/ Load done /")
print("/////////////////////////////////////")
def Auto_Clicker(self):
print("set auto click")
global EXECUTE_CLICK_BIGCOOKIE, EXECUTE_CLICK_GOLDENCOOKIE
self.driver.execute_script(EXECUTE_CLICK_BIGCOOKIE)
self.driver.execute_script(EXECUTE_CLICK_GOLDENCOOKIE)
def Buy_allUpgrades(self):
print("Buy all upgrades")
while True:
try:
upgrade = self.driver.find_element_by_id("upgrade0")
if "enabled" in upgrade.get_attribute("class"):
upgrade.click()
else:
return
except:
# print(something happened : at buyallupgrades)
return
def Standard_Strategy(self):
# productsの中で、Cps_per_priceの高いもの順に並び替え
temp = sorted(self.products, key=lambda p:-p.Cps_per_price)
count = 0
max_count = 3
buythem = []
# Cps_per_priceの高い順に、unlockされているものを4つ持ってくる
for p in temp:
if p.is_unlocked:
buythem.append(p)
count += 1
if count > max_count:
break
print("Standard Strategy begin")
for p in buythem:
if p.is_active(self.driver):
# NUMBER_OF_BUYだけ購入
p.Buy(CookieCliker.NUMBER_OF_BUY, self.driver)
print("Standard Strategy end")
def Run(self):
print("#####################################")
print("# Running CookieCliker #")
print("#####################################")
self.Import_Savedata()
self.Auto_Clicker()
fieldnames = ("time","Cookies", "Cps")
save_point = 0
data = [0, 0, 0]
start = time.time()
while True:
self.Buy_allUpgrades()
self.Standard_Strategy()
print("wait" + str(CookieCliker.INTERVAL) + "sec")
sleep(CookieCliker.INTERVAL)
save_point += 1
if save_point > 10:
self.Export_Savedata()
data = {}
data["time"] = time.time() - start
data["Cookies"] = self.driver.execute_script("return Game.cookies")
data["Cps"] = self.driver.execute_script("return Game.cookiesPs")
save_point = 0
with open("log.csv", "a", newline="") as log:
temp = csv.DictWriter(log, fieldnames=fieldnames)
temp.writerow(data)
print("--------------------------------------")
clicker = CookieCliker(URL_CookieClicker)
clicker.Run()
print("cookie Clicker end")
class Building(object):
product = "product"
def __init__(self, name ,id , driver):
self.id = id
self.productid = "product" + str(id)
self.name = name
self.execute_ThisBuilding = "Game.ObjectsById[" + str(id) + "]"
self.Cps = driver.execute_script("return " + self.execute_ThisBuilding + ".storedCps;")
self.price = driver.execute_script("return " + self.execute_ThisBuilding + ".price;")
self.Cps_per_price = self.Cps / self.price
def Buy(self, bought_number, driver):
# クリックさせるよりも高速
driver.execute_script(self.execute_ThisBuilding + ".buy(" + str(bought_number) + ");")
self.Cps = driver.execute_script("return " + self.execute_ThisBuilding + ".storedCps;")
self.price = driver.execute_script("return " + self.execute_ThisBuilding + ".price;")
self.Cps_per_price = self.Cps / self.price
def Sell(self, sold_number, driver):
driver.execute_script(self.execute_ThisBuilding + ".sell(" + str(bought_number) + ");")
self.Cps = driver.execute_script("return " + self.execute_ThisBuilding + ".storedCps;")
self.price = driver.execute_script("return " + self.execute_ThisBuilding + ".price;")
self.Cps_per_price = self.Cps / self.price
def is_active(self, driver):
return "enabled" in driver.find_element_by_id(self.productid).get_attribute("class")
def is_unlocked(self, driver):
return "unlocked" in driver.find_element_by_id(self.productid).get_attribute("class")
def Update(self, driver):
self.Cps = driver.execute_script("return " + self.execute_ThisBuilding + ".storedCps;")
self.price = driver.execute_script("return " + self.execute_ThisBuilding + ".price;")
self.Cps_per_price = self.Cps / self.price
少し解説。
git_commit
Gitpythonを使って、pythonからsaveリポジトリのコミットをさせる関数。
自動化しつつセーブデータを個別に保存すると膨大な数のセーブデータが生まれてしまうので、今回はセーブデータ部分をgitで管理することにした。
GitPythonを使うを参考にしました。
Building
基本的に、時間経過のみで変更のない情報(Cps, price)のみを保有し
残りは適宜javascriptを走らせて調べるようにしました。
……プログラム設計的には、どうした方がよいのでしょうかね??
CookieClicker
今のところそんなに煩雑なことはしないので、ただFirefoxを起動してjavascriptを走らせているだけ。
javascriptはCookieClickerのソースコードを読んで持ってきました。
セーブデータ管理(Import/Export_Savedata)
Cookie ClickerのOptionsをクリックして~という単純な方法を取ってもよかったのですが、javascriptを読んでみると以下のようにすればデータのロードができるようになることがわかりました。
Game.ImportSave();
// id = textareaPrompt のテキストエリアが出現
// id = textareaPrompt のテキストエリアにセーブデータを入力する
Game.ImportSaveCode(l('textareaPrompt').value);
//セーブデータがロードされる
Game.ClosePrompt();
// テキストエリアを閉じる
同様に、Exportも以下のようにすれば良いことがわかりました。
Game.ExportSave();
// id = textareaPrompt のテキストエリアが出現
// id = textareaPrompt のテキストエリアのセーブデータを保存
Game.ClosePrompt();
// テキストエリアを閉じる
自動アップグレード購入(Buy_allUpgrades)
アップグレードには、"id = upgradeX" というidが振られており(Xには0以上の整数)
購入できるものには"class enabled"が振られていました。 なので、"id = upgrade0"のアップグレードが"class enabled"を保有しているときに購入、という購入をできる限り行うようにさせました。
自動Building購入(Standard_Strategy)
一番悩みましたが、結局「(unclock済みの中で)Cps/priceの高い順に並べた上位4位の中で、購入可能なbuildingをできる限り購入する」という単純な手法を取りました。
ただし、この方法だと
- アップグレードの順番考慮0
- シナジーも考慮0
- なぞの新要素(ドラゴンエッグやSugar lumps)
- 後半、Cpsが全く伸びない(Building購入に時間がかかるため)
などの問題が発生して全然効率的じゃないです……(購入タイミングも含めて)
感想
クッキークリッカーたのしい