mirror of
https://github.com/0O0o0oOoO00/Alas.git
synced 2026-05-14 08:59:25 +08:00
467 lines
18 KiB
Python
467 lines
18 KiB
Python
import csv
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
import glob
|
|
import os
|
|
from typing import *
|
|
from copy import deepcopy
|
|
import pandas as pd
|
|
import matplotlib
|
|
matplotlib.use('Agg')
|
|
import matplotlib.pyplot as plt
|
|
# from matplotlib.dates import DateFormatter
|
|
|
|
from typing import Dict
|
|
|
|
|
|
class ResourcesReport:
|
|
class Record:
|
|
def __init__(self, time, value):
|
|
if isinstance(time, datetime):
|
|
self.time = time
|
|
if isinstance(time, str):
|
|
self.time = datetime.strptime(time, "%Y-%m-%d %H:%M:%S.%f")
|
|
self.value = float(value)
|
|
|
|
CSV_HEADER = ["Time", "Value"]
|
|
|
|
def __init__(self, config):
|
|
self.records_to_add : Dict = {}
|
|
self.config: Dict = config
|
|
if self.config['resample_rate'] == 'H':
|
|
self.config['timedelta'] = timedelta(hours=1)
|
|
elif self.config['resample_rate'] == 'D':
|
|
self.config['timedelta'] = timedelta(days=1)
|
|
elif self.config['resample_rate'] == 'M':
|
|
self.config['timedelta'] = timedelta(minutes=1)
|
|
else:
|
|
exit("Invalid sample rate")
|
|
self.total_gem_expected_value = 0
|
|
self.gem_expected_csv_records = list()
|
|
self.action_point_csv_records: List[ResourcesReport.Record] = list()
|
|
self.oil_csv_records: List[ResourcesReport.Record] = list()
|
|
self.gem_csv_records: List[ResourcesReport.Record] = list()
|
|
self.coin_csv_records: List[ResourcesReport.Record] = list()
|
|
self.cube_csv_records: List[ResourcesReport.Record] = list()
|
|
self.pt_csv_records: List[ResourcesReport.Record] = list()
|
|
self.yellow_coin_csv_records: List[ResourcesReport.Record] = list()
|
|
self.purple_coin_csv_records: List[ResourcesReport.Record] = list()
|
|
self.player_exp_csv_records: List[ResourcesReport.Record] = list()
|
|
self.records_dict : Dict[str: List[ResourcesReport.Record]] = {
|
|
'oil' :self.oil_csv_records,
|
|
'gem' :self.gem_csv_records,
|
|
'gem_expected' :self.gem_expected_csv_records,
|
|
'coin' :self.coin_csv_records,
|
|
'cube' :self.cube_csv_records,
|
|
'pt' :self.pt_csv_records,
|
|
'yellow_coin' :self.yellow_coin_csv_records,
|
|
'purple_coin' :self.purple_coin_csv_records,
|
|
'player_exp' :self.player_exp_csv_records,
|
|
'action_point' :self.action_point_csv_records,
|
|
}
|
|
|
|
def process_lines(self, lines):
|
|
lines = iter(lines)
|
|
for line in lines:
|
|
# oil
|
|
if line.find("[OCR_OIL_LIMIT") != -1:
|
|
continue
|
|
if line.find("[OCR_OIL") != -1:
|
|
line_splited = line.split(" | ")
|
|
time_str, value_str = line_splited[0], line_splited[2].rstrip().split(" ")[2]
|
|
self.oil_csv_records.append(self.Record(time_str, value_str))
|
|
|
|
# gem
|
|
if line.find("[SHOP_GEMS") != -1:
|
|
line_splited = line.split(" | ")
|
|
time_str, value_str = line_splited[0], line_splited[2].rstrip().split(" ")[2]
|
|
self.gem_csv_records.append(self.Record(time_str, value_str))
|
|
|
|
# coin
|
|
if line.find("[OCR_COIN_LIMIT") != -1:
|
|
continue
|
|
if line.find("[OCR_COIN") != -1:
|
|
line_splited = line.split(" | ")
|
|
time_str, value_str = line_splited[0], line_splited[2].rstrip().split(" ")[2]
|
|
self.coin_csv_records.append(self.Record(time_str, value_str))
|
|
|
|
# cube
|
|
if line.find("[BUILD_CUBE_COUNT") != -1:
|
|
line_splited = line.split(" | ")
|
|
time_str, value_str = line_splited[0], line_splited[2].rstrip().split(" ")[2]
|
|
self.cube_csv_records.append(self.Record(time_str, value_str))
|
|
|
|
# pt
|
|
if line.find("[Event_PT_limit]") != -1:
|
|
continue
|
|
if line.find("[Event_PT]") != -1:
|
|
line_splited = line.split(" | ")
|
|
time_str, value_str = line_splited[0], line_splited[2].rstrip().split(" ")[1]
|
|
self.pt_csv_records.append(self.Record(time_str, value_str))
|
|
|
|
# yellow_coin
|
|
if line.find("[SHOP_YELLOW_COINS") != -1:
|
|
line_splited = line.split(" | ")
|
|
time_str, value_str = line_splited[0], line_splited[2].rstrip().split(" ")[2]
|
|
self.yellow_coin_csv_records.append(self.Record(time_str, value_str))
|
|
|
|
# purple_coin
|
|
if line.find("[SHOP_PURPLE_COINS") != -1:
|
|
line_splited = line.split(" | ")
|
|
time_str, value_str = line_splited[0], line_splited[2].rstrip().split(" ")[2]
|
|
self.purple_coin_csv_records.append(self.Record(time_str, value_str))
|
|
|
|
# player_exp
|
|
if line.find("[OCR_PLAYER_EXP") != -1:
|
|
line_splited = line.split(" | ")
|
|
time_str, value_str = line_splited[0], line_splited[2].rstrip().split(" ")[2].split("/")[0]
|
|
self.player_exp_csv_records.append(self.Record(time_str, value_str))
|
|
|
|
# action_points
|
|
if line.find(" | INFO | Action points:") != -1:
|
|
line_splited = line.split(" | ")
|
|
time_str, value_str = line_splited[0], line_splited[2].rstrip().split(" ")[2]
|
|
value_str = value_str.split("(")[1].split(")")[0]
|
|
self.action_point_csv_records.append(self.Record(time_str, value_str))
|
|
|
|
if line.find("Choose urgent commission") != -1:
|
|
line = next(lines,"EoL")
|
|
if "gem" in line:
|
|
if "Duration: 2:00:00" in line:
|
|
self.total_gem_expected_value += 5.5
|
|
if "Duration: 4:00:00" in line:
|
|
self.total_gem_expected_value += 12
|
|
if "Duration: 8:00:00" in line:
|
|
self.total_gem_expected_value += 22.5
|
|
line_splited = line.split(" | ")
|
|
time_str, value_str = line_splited[0], str(self.total_gem_expected_value)
|
|
self.gem_expected_csv_records.append(self.Record(time_str, value_str))
|
|
|
|
def sort_records(self):
|
|
for name,records in self.records_dict.items():
|
|
records.sort(key=lambda x: x.time)
|
|
|
|
|
|
def resample(self, records, name):
|
|
|
|
|
|
# 重要!!!我不知道怎么动起来的,搞不懂就别动!!!
|
|
|
|
|
|
if self.config['resample_rate'] == 'D':
|
|
start_time = datetime(records[0].time.year, records[0].time.month, records[0].time.day)+self.config['timedelta']
|
|
elif self.config['resample_rate'] == 'H':
|
|
start_time = datetime(records[0].time.year, records[0].time.month, records[0].time.day, records[0].time.hour)+self.config['timedelta']
|
|
elif self.config['resample_rate'] == 'M':
|
|
start_time = datetime(records[0].time.year, records[0].time.month, records[0].time.day, records[0].time.hour, records[0].time.minute)+self.config['timedelta']
|
|
_ = start_time
|
|
last_record = records[0]
|
|
records_copy = records.copy()
|
|
for record in records_copy:
|
|
if record.time > _ >= last_record.time:
|
|
while True:
|
|
if _ >= record.time:
|
|
break
|
|
if _ - self.config['timedelta']< last_record.time <= _:
|
|
_record = self.Record(_, last_record.value)
|
|
records.append(_record)
|
|
last_record = record
|
|
|
|
_ += self.config['timedelta']
|
|
_record = self.Record(_, record.value)
|
|
records.remove(record)
|
|
records.append(_record)
|
|
_ += self.config['timedelta']
|
|
continue
|
|
if record.time <= _:
|
|
if record == records_copy[-1]:
|
|
_record = self.Record(_, record.value)
|
|
records.remove(record)
|
|
records.append(_record)
|
|
break
|
|
last_record = record
|
|
records.remove(record)
|
|
continue
|
|
|
|
if name == 'gem':
|
|
records_copy = deepcopy(records)
|
|
d = -records_copy[0].value
|
|
records_copy[0] = self.Record(records_copy[0].time,0)
|
|
l = len(records)
|
|
for _ in range(1,l):
|
|
record=records[_]
|
|
last_record = records[_-1]
|
|
delta_v = record.value - last_record.value
|
|
if delta_v > 150 or delta_v < 0:
|
|
d -= delta_v
|
|
records_copy[_].value = records_copy[_].value +d
|
|
self.records_to_add.update({'gem_modified' : records_copy})
|
|
|
|
if name == 'pt':
|
|
new_records = []
|
|
new_count = 1
|
|
l = len(records)
|
|
for _ in range(1,l):
|
|
record=records[_]
|
|
last_record = records[_-1]
|
|
new_records.append(last_record)
|
|
if record.value < last_record.value:
|
|
self.records_to_add.update({f'pt_event_{new_count}': new_records})
|
|
new_count += 1
|
|
new_records = []
|
|
last_record = record
|
|
new_records.append(last_record)
|
|
if new_count > 1:
|
|
self.records_to_add.update({f'pt_event_{new_count}' : new_records})
|
|
|
|
|
|
|
|
def pre_draw_reshape(self, records):
|
|
inserted = 0
|
|
records_copy = records.copy()
|
|
for _ in range(1,len(records_copy)):
|
|
if records_copy[_].time - records_copy[_-1].time > self.config['timedelta']:
|
|
time_delta = (records_copy[_].time - records_copy[_-1].time)//self.config['timedelta']
|
|
value_delta = (records_copy[_].value - records_copy[_-1].value)/time_delta
|
|
for __ in range(1,time_delta):
|
|
records.insert(_+inserted, self.Record(records_copy[_-1].time + __*self.config['timedelta'] ,
|
|
records_copy[_-1].value + __*value_delta))
|
|
inserted += 1
|
|
|
|
def draw_plt(self, records, name):
|
|
png_dir = f"./report/{self.config['config']}/PNG"
|
|
if not os.path.exists(png_dir):
|
|
os.makedirs(png_dir)
|
|
if "gem" in name:
|
|
if name == "gem":
|
|
pass
|
|
if "expected" in name:
|
|
print(f"Post process - Plot Drawing Gem_get")
|
|
df1 = pd.DataFrame({
|
|
"time": [record.time for record in records],
|
|
"Expected": [record.value for record in records]
|
|
})
|
|
df2 = pd.DataFrame({
|
|
"time": [record.time for record in self.records_dict['gem_modified']],
|
|
"Real": [record.value for record in self.records_dict['gem_modified']]
|
|
})
|
|
|
|
df1.set_index("time", inplace=True)
|
|
df2.set_index("time", inplace=True)
|
|
|
|
df_combined = pd.merge(
|
|
df1,
|
|
df2,
|
|
on="time",
|
|
how="outer"
|
|
).sort_values("time")
|
|
df_combined = df_combined.fillna(method="ffill")
|
|
df_combined = df_combined.fillna(method="bfill")
|
|
|
|
width_needed = len(records) * 0.0125 if self.config['config'] in ["H", "M"] else len(records) * 0.3
|
|
width_max = 2 ^ 32
|
|
if width_needed > width_max:
|
|
width = width_max
|
|
else:
|
|
width = width_needed
|
|
width = max(10, width)
|
|
height = width / 2
|
|
|
|
plt.figure(figsize=(width, height))
|
|
|
|
plt.plot(df_combined.index, df_combined["Real"], label="Real")
|
|
plt.plot(df_combined.index, df_combined["Expected"], label="Expected")
|
|
plt.legend()
|
|
plt.xlabel("Time")
|
|
plt.ylabel("Value")
|
|
plt.title(f"{self.config['config']} - Gem_get Over Time")
|
|
|
|
plt.xticks(rotation=45)
|
|
plt.tight_layout()
|
|
|
|
plt.savefig(f"{png_dir}/Gem_get.png", dpi=150, bbox_inches="tight")
|
|
plt.close()
|
|
|
|
return
|
|
if "modified" in name:
|
|
return
|
|
|
|
print(f"Post process - Plot Drawing {name.capitalize()}")
|
|
df = pd.DataFrame({
|
|
"time": [record.time for record in records],
|
|
name.capitalize(): [record.value for record in records]
|
|
})
|
|
df.set_index("time", inplace=True)
|
|
|
|
width_needed = len(records) * 0.0125 if self.config['config'] in ["H","M"] else len(records) * 0.3
|
|
width_max = 2^32
|
|
if width_needed > width_max:
|
|
width = width_max
|
|
else:
|
|
width = width_needed
|
|
width = max(10, width)
|
|
height = width/2
|
|
ax = df.plot(
|
|
kind="line",
|
|
style="-",
|
|
figsize=(width, height),
|
|
title=f"{self.config['config']} - {name.capitalize()} Over Time",
|
|
grid=True,
|
|
)
|
|
ax.set_xlabel("time")
|
|
ax.set_ylabel(name.capitalize())
|
|
|
|
plt.xticks(rotation=45)
|
|
plt.tight_layout()
|
|
|
|
plt.savefig(f"{png_dir}/{name.capitalize()}.png", dpi=150, bbox_inches="tight")
|
|
plt.close()
|
|
|
|
def post_process(self):
|
|
print(f"Post process - Sorting")
|
|
self.sort_records()
|
|
for name, records in self.records_dict.items():
|
|
if not records:
|
|
continue
|
|
print(f"Post process - Resampling {name.capitalize()}")
|
|
self.resample(records, name)
|
|
if self.config['resample_rate'] == "M":
|
|
if len(records) >= 24 * 60:
|
|
# and name in [
|
|
# 'oil',
|
|
# 'coin',
|
|
# 'gem',
|
|
# 'gem_modified',
|
|
# ]:
|
|
self.config['resample_rate'] = "H"
|
|
self.config['timedelta'] = timedelta(hours=1)
|
|
self.resample(records, name)
|
|
print(f'WARNING - {name.capitalize()} has too many records, forced resample to hourly statistics.')
|
|
self.config['resample_rate'] = "M"
|
|
self.config['timedelta'] = timedelta(minutes=1)
|
|
if self.config['resample_rate'] == "H":
|
|
if len(records)>=60*24:
|
|
# and name in [
|
|
# 'oil',
|
|
# 'coin',
|
|
# 'gem',
|
|
# 'gem_modified',
|
|
# ]:
|
|
self.config['resample_rate'] = "D"
|
|
self.config['timedelta'] = timedelta(days=1)
|
|
self.resample(records, name)
|
|
print(f'WARNING - {name.capitalize()} has too many records, forced resample to daily statistics.')
|
|
self.config['resample_rate'] = "H"
|
|
self.config['timedelta'] = timedelta(hours=1)
|
|
self.records_dict.update(self.records_to_add)
|
|
# if 'pt_event_1' in self.records_dict.keys():
|
|
# self.records_dict.pop('pt')
|
|
|
|
for name, records in self.records_dict.items():
|
|
if not records:
|
|
continue
|
|
|
|
self.draw_plt(records, name)
|
|
|
|
|
|
def dump_single(self, f, l):
|
|
with open(f, mode="w", encoding="utf-8", newline="") as fd:
|
|
writer = csv.writer(fd)
|
|
writer.writerow(self.CSV_HEADER)
|
|
writer.writerows([
|
|
[i.time.strftime("%Y-%m-%d %H:%M:%S.%f"), str(i.value)]
|
|
for i in l
|
|
])
|
|
|
|
def dump_to_file(self):
|
|
config_report_dir = f"./report/{self.config['config']}"
|
|
if not os.path.exists(config_report_dir):
|
|
os.makedirs(config_report_dir)
|
|
print(f"Finishing - Generate report to {config_report_dir}")
|
|
|
|
csv_dir = f"./report/{self.config['config']}/CSV"
|
|
if not os.path.exists(csv_dir):
|
|
os.makedirs(csv_dir)
|
|
|
|
for name, records in self.records_dict.items():
|
|
self.dump_single(f"{csv_dir}/{name.capitalize()}.csv", records)
|
|
|
|
print(f"Finished - Report generated")
|
|
|
|
def gen_res_report(self):
|
|
l = glob.glob(f"./log/*_{self.config['config']}.txt")
|
|
|
|
for log in l:
|
|
print(f"Line process - {log} - {os.stat(log).st_size} Bytes")
|
|
|
|
with open(log, mode="r", encoding="utf-8") as f:
|
|
lines = f.readlines()
|
|
self.process_lines(lines)
|
|
self.post_process()
|
|
self.dump_to_file()
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("Invalid args.")
|
|
exit(-1)
|
|
_ = 1
|
|
conf = {
|
|
"config": "",
|
|
"resample_rate": ""
|
|
}
|
|
while _ < len(sys.argv):
|
|
arg = sys.argv[_]
|
|
if arg in ["--config-name", "--config", "-c"]:
|
|
if _+1 >= len(sys.argv):
|
|
exit("Invalid args.")
|
|
conf["config"] = sys.argv[_+1]
|
|
_ += 2
|
|
continue
|
|
if arg in ["--resample-rate","-r"]:
|
|
if _+1 >= len(sys.argv):
|
|
exit("Invalid resample rate.")
|
|
conf["resample_rate"] = sys.argv[_+1]
|
|
_ += 2
|
|
continue
|
|
if arg.upper() in ["M","D","H","HOUR","MINUTE","DAY","24"] and len(sys.argv) >2 and conf["resample_rate"] == ""\
|
|
and _+1 < len(sys.argv) and sys.argv[_+1].upper() not in ["M","D","HOUR","MINUTE","DAY","24"]\
|
|
and sys.argv[_+1] not in ["--resample-rate","-r"]:
|
|
conf["resample_rate"] = arg
|
|
_ += 1
|
|
continue
|
|
if arg.upper() in ["M","D","H","HOUR","MINUTE","DAY","24"] and conf["config"] != ""\
|
|
and _+1 == len(sys.argv) :
|
|
conf["resample_rate"] = arg
|
|
_ += 1
|
|
continue
|
|
if conf['config'] == "":
|
|
conf["config"] = arg
|
|
_ += 1
|
|
else:
|
|
exit("Invalid args.")
|
|
if conf["resample_rate"].upper() in ["M",'MINUTE']:
|
|
conf["resample_rate"] = 'M'
|
|
elif conf["resample_rate"].upper() in ["H",'HOUR','']:
|
|
conf["resample_rate"] = 'H'
|
|
elif conf["resample_rate"] .upper() in ["D","DAY",'24']:
|
|
conf["resample_rate"] = 'D'
|
|
else:
|
|
exit("Invalid resample rate.")
|
|
|
|
rate ={
|
|
'M' : 'Per Minute',
|
|
'D' : 'Per Day',
|
|
'H' : 'Per Hour',
|
|
}
|
|
|
|
print(f"Starting with config: \n"
|
|
f"Config name : {conf['config']}\n"
|
|
f"Resample rate: {rate[conf['resample_rate']]}")
|
|
reporter = ResourcesReport(conf)
|
|
reporter.gen_res_report()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|