aigurletov commited on
Commit
313d10b
·
1 Parent(s): 516364e

gemini recreate merge balance tool

Browse files
Files changed (2) hide show
  1. README.md +26 -0
  2. app.py +257 -4
README.md CHANGED
@@ -11,3 +11,29 @@ short_description: Набор инструментов для баланса м
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
14
+
15
+
16
+ Задача сделать тулзу для удобной работы с балансом мердж-2 игры.
17
+
18
+ 1) Одна из основных фичей тулзы это Симуляция работы Генератора Заказов на базе конфигов с его правилами и конфигов мердж-цепочек. На выходе мы получаем таблицу с данными по симуляции и небольшой текстовый файл с сводными данными.
19
+
20
+ 2) В этой тулзе также можно менять параметры разных мердж конфигов - опять же если нужно протестировать значения. Значит важно чтобы мы могли загружать оригинальные файлы конфигов (.asset) и в интерфейсе удобно автоматически раскладывать их по типу и сразу исключая лишние данные (например ключи переводов и тд) - оставлять только важные параметры влияющие на баланс игры.
21
+
22
+ 3) Расчет стоимости предметов мердж цепочки исходя из весов предметов требуемых для производства других уровней предметов цепочки выпадающих из спавнеров.
23
+
24
+
25
+
26
+ MergeOrderSimulationService - проверенный, рабочий в юнити симулятор генератора заказов который нам нужно переписать под новый инструмент.
27
+
28
+ MergeChainCostCalculator - инструмент для расчета стоимости в энергии предметов мердж цепочек. Скрипт пока не работает должным образом. Хорошо если мы его починим для нашего инструмента.
29
+
30
+
31
+
32
+ Далее.
33
+
34
+ Наша задача взять все эти данные, сделать анализ конфигов для импорта, забрать наработки, сделать удобный интерфейс и произвести стабильный расширяемый при необходимости инструмент для балансировки игровых механик.
35
+
36
+
37
+
38
+ Я хочу, чтобы инструмент был сделан реализован на библиотеке и компонентах gradio.app на python.
39
+
app.py CHANGED
@@ -1,7 +1,260 @@
1
  import gradio as gr
 
 
 
 
 
2
 
3
- def greet(name):
4
- return "Hello " + name + "!!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- demo = gr.Interface(fn=greet, inputs="text", outputs="text")
7
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ import pandas as pd
3
+ import yaml
4
+ import io
5
+ import random
6
+ from collections import deque
7
 
8
+ # HELPER FUNCTIONS TO PARSE .asset FILES
9
+ def parse_asset_file(file):
10
+ """Parses a YAML-like .asset file, skipping the initial %YAML and %TAG lines."""
11
+ if file is None:
12
+ return None
13
+ try:
14
+ content = file.decode('utf-8')
15
+ # Find the first '---' which indicates the start of the YAML document
16
+ yaml_start = content.find('---')
17
+ if yaml_start == -1:
18
+ raise ValueError("Not a valid Unity .asset file (missing '---')")
19
+
20
+ # Clean up the content for standard YAML parsing
21
+ yaml_content = content[yaml_start:]
22
+ # Remove the MonoBehaviour header which is not valid YAML
23
+ lines = yaml_content.split('\n')
24
+ cleaned_lines = [line for line in lines if not line.strip().startswith('MonoBehaviour:')]
25
+ cleaned_yaml = '\n'.join(cleaned_lines)
26
 
27
+ # The file seems to have a custom serialization format within 'serializationData'.
28
+ # For now, we will parse the main structure.
29
+ # A more robust solution might need a dedicated parser for Unity's format.
30
+
31
+ # Let's try to parse the main body first
32
+ data = yaml.safe_load(io.StringIO(cleaned_yaml))
33
+
34
+ # A simplified approach to extract key-value pairs for now
35
+ # This will need to be much more robust based on the actual file structure.
36
+ config_data = {}
37
+ for line in content.split('\n'):
38
+ if ':' in line:
39
+ parts = line.split(':', 1)
40
+ key = parts[0].strip()
41
+ value = parts[1].strip()
42
+ if key.startswith('_'):
43
+ config_data[key] = value
44
+
45
+ return config_data, data
46
+ except Exception as e:
47
+ print(f"Error parsing file: {e}")
48
+ return None, None
49
+
50
+ # --- SIMULATION LOGIC ---
51
+ def run_simulation_logic(ruleset_file, chain_files, iteration_count, initial_energy):
52
+ """
53
+ This function will contain the Python port of the C# MergeOrderSimulationService.
54
+ For now, it returns placeholder data.
55
+ """
56
+ # Placeholder for parsed ruleset
57
+ ruleset_id = "ruleset_placeholder"
58
+
59
+ # Placeholder for selected chain IDs
60
+ selected_chain_ids = [f"chain_{i}" for i in range(len(chain_files))]
61
+
62
+ # --- This is where the core simulation logic will go ---
63
+ # We will replicate the steps from MergeOrderSimulationService.cs:
64
+ # 1. Get all available items from selected chains.
65
+ # 2. Apply MergeBoardItemFinder simulation.
66
+ # 3. Filter by unlock level.
67
+ # 4. Filter recently used items.
68
+ # 5. Generate order with proper difficulty.
69
+ # 6. Update history.
70
+ # 7. Calculate costs and rewards.
71
+ # 8. Store data for CSV.
72
+
73
+ # For now, let's generate some dummy data to show in the UI
74
+ sim_data = []
75
+ current_energy = initial_energy
76
+ difficulty_history = deque(maxlen=5) # Assuming max history of 5 from ruleset
77
+
78
+ for i in range(iteration_count):
79
+ req1 = f"merge_item_wood_branch_{random.randint(1, 3)}"
80
+ req2 = f"merge_item_stones_stones_{random.randint(1, 2)}" if random.random() > 0.5 else ""
81
+ energy_cost = random.randint(5, 20)
82
+ current_energy -= energy_cost
83
+ total_difficulty = random.randint(500, 2000)
84
+ difficulty_history.append(total_difficulty)
85
+
86
+ sim_data.append({
87
+ "Order": i + 1,
88
+ "MEnergy_Price": energy_cost,
89
+ "MEnergy_Amount": current_energy,
90
+ "Requirement_1": req1,
91
+ "Weight_1": random.randint(50, 150),
92
+ "ChainId_1": "orders_merge_wood",
93
+ "Level_1": int(req1.split('_')[-1]),
94
+ "Requirement_2": req2,
95
+ "Weight_2": random.randint(75, 125) if req2 else 0,
96
+ "ChainId_2": "merge_stones" if req2 else "",
97
+ "Level_2": int(req2.split('_')[-1]) if req2 else 0,
98
+ "Total_Difficulty": total_difficulty,
99
+ "Adjusted_Difficulty": total_difficulty + sum(list(difficulty_history)[:-1]),
100
+ })
101
+
102
+ df = pd.DataFrame(sim_data)
103
+
104
+ # Placeholder for statistics report
105
+ stats_report = f"""
106
+ SIMULATION STATISTICS
107
+ =====================
108
+ Total Orders Generated: {iteration_count}
109
+ Average Difficulty: {df['Total_Difficulty'].mean():.2f}
110
+
111
+ Item Frequency (Top 5):
112
+ {df['Requirement_1'].value_counts().head(5).to_string()}
113
+ """
114
+
115
+ return df, stats_report, "simulation_results.csv", "simulation_stats.txt"
116
+
117
+ def run_simulation_interface(ruleset_file, chain_files, iteration_count, initial_energy):
118
+ if not ruleset_file or not chain_files:
119
+ raise gr.Error("Пожалуйста, загрузите файл правил и хотя бы один файл цепочки.")
120
+
121
+ df, stats_report, df_path, report_path = run_simulation_logic(
122
+ ruleset_file, chain_files, iteration_count, initial_energy
123
+ )
124
+
125
+ # Convert DataFrame to a downloadable CSV file
126
+ csv_buffer = io.StringIO()
127
+ df.to_csv(csv_buffer, index=False)
128
+ csv_bytes = csv_buffer.getvalue().encode('utf-8')
129
+
130
+ return df, stats_report, gr.File.update(value=csv_bytes, visible=True, name=df_path), gr.File.update(value=stats_report.encode('utf-8'), visible=True, name=report_path)
131
+
132
+ # --- COST CALCULATION LOGIC ---
133
+ def run_cost_calculation_logic(chain_files, item_files):
134
+ """
135
+ This function will contain the Python port of the C# MergeChainCostCalculator.
136
+ For now, it returns placeholder data.
137
+ """
138
+ # --- This is where the core cost calculation logic will go ---
139
+ # We will replicate the steps from MergeChainCostCalculator.cs:
140
+ # 1. Load and parse all merge chain and item configs.
141
+ # 2. For each chain, find its associated producer (spawner).
142
+ # 3. For each item in the chain, calculate the expected number of attempts to get it.
143
+ # 4. Calculate the final energy cost (expected attempts * spawn cost).
144
+ # 5. Handle dynamic weights (IncWeight).
145
+
146
+ # Dummy data for now
147
+ cost_data = []
148
+ for i, chain_file in enumerate(chain_files):
149
+ chain_id = f"chain_{i+1}"
150
+ for level in range(1, 7):
151
+ cost_data.append({
152
+ "Chain ID": chain_id,
153
+ "Item ID": f"{chain_id}_item_{level}",
154
+ "Level": level,
155
+ "Producer ID": f"spawner_{i+1}",
156
+ "Spawn Cost": 5,
157
+ "Weight": 100 - (level * 10),
158
+ "Inc Weight": 0,
159
+ "Expected Attempts": 1.0 + (level * 0.5),
160
+ "Energy Cost": (1.0 + (level * 0.5)) * 5,
161
+ "Warning": ""
162
+ })
163
+
164
+ df = pd.DataFrame(cost_data)
165
+ return df
166
+
167
+ def run_cost_calculation_interface(chain_files, item_files):
168
+ if not chain_files or not item_files:
169
+ raise gr.Error("Пожалуйста, загрузите файлы цепочек и предметов.")
170
+
171
+ df = run_cost_calculation_logic(chain_files, item_files)
172
+ return df
173
+
174
+ # --- CONFIG EDITOR LOGIC ---
175
+ def view_config_file(file):
176
+ if file is None:
177
+ return "Загрузите файл для просмотра.", ""
178
+
179
+ raw_content = file.decode('utf-8')
180
+
181
+ # A simple display of the raw content.
182
+ # In a real scenario, we would parse this into editable components.
183
+ return raw_content, os.path.basename(file.name)
184
+
185
+ # --- GRADIO UI ---
186
+ with gr.Blocks(theme=gr.themes.Soft(), title="Инструмент Балансировки Merge-2") as demo:
187
+ gr.Markdown("# Инструмент для Балансировки Игр Merge-2")
188
+
189
+ with gr.Tabs():
190
+ # --- SIMULATION TAB ---
191
+ with gr.TabItem("Симуляция генератора заказов"):
192
+ gr.Markdown("## Симуляция генератора заказов\nЗагрузите конфиги и запустите симуляцию для получения данных по генерации заказов.")
193
+ with gr.Row():
194
+ with gr.Column(scale=1):
195
+ sim_ruleset_upload = gr.File(label="Загрузить Ruleset (.asset)")
196
+ sim_chains_upload = gr.File(label="Загрузить файлы цепочек (.asset)", file_count="multiple")
197
+ sim_iterations = gr.Slider(10, 1000, value=100, step=10, label="Количество итераций")
198
+ sim_initial_energy = gr.Number(value=10000, label="Начальное количество энергии")
199
+ sim_run_button = gr.Button("Запустить симуляцию", variant="primary")
200
+ with gr.Column(scale=3):
201
+ gr.Markdown("### Результаты симуляции")
202
+ sim_results_df = gr.DataFrame(label="Данные по заказам")
203
+ gr.Markdown("### Сводный отчет")
204
+ sim_stats_report = gr.Textbox(label="Статистика", lines=10)
205
+ with gr.Row():
206
+ sim_download_csv = gr.File(label="Скачать CSV", visible=False)
207
+ sim_download_report = gr.File(label="Скачать отчет", visible=False)
208
+
209
+ # --- COST CALCULATION TAB ---
210
+ with gr.TabItem("Расчет стоимости цепочек"):
211
+ gr.Markdown("## Расчет стоимости предметов\nЗагрузите конфиги цепочек и спавнеров для расчета стоимости производства предметов в энергии.")
212
+ with gr.Row():
213
+ with gr.Column(scale=1):
214
+ cost_chains_upload = gr.File(label="Загрузить файлы цепочек (.asset)", file_count="multiple")
215
+ cost_items_upload = gr.File(label="Загрузить файлы спавнеров/предметов (.asset)", file_count="multiple")
216
+ cost_run_button = gr.Button("Рассчитать стоимость", variant="primary")
217
+ with gr.Column(scale=3):
218
+ gr.Markdown("### Рассчитанная стоимость")
219
+ cost_results_df = gr.DataFrame(label="Стоимость предметов в энергии")
220
+
221
+ # --- CONFIG EDITOR TAB ---
222
+ with gr.TabItem("Редактор конфигов"):
223
+ gr.Markdown("## Редактор Конфигурационных Файлов\nЗагрузите файл для просмотра и редактирования его параметров.")
224
+ with gr.Row():
225
+ with gr.Column(scale=1):
226
+ config_upload = gr.File(label="Загрузить файл .asset для редактирования")
227
+ config_file_name = gr.Label(label="Выбранный файл")
228
+ with gr.Column(scale=2):
229
+ # For now, just a textbox. Could be replaced with dynamic fields.
230
+ config_content_viewer = gr.Textbox(label="Содержимое файла", lines=30, interactive=True)
231
+ config_save_button = gr.Button("Сохранить изменения (не реализовано)", interactive=False)
232
+
233
+ # --- Event Handlers ---
234
+ sim_run_button.click(
235
+ fn=run_simulation_interface,
236
+ inputs=[sim_ruleset_upload, sim_chains_upload, sim_iterations, sim_initial_energy],
237
+ outputs=[sim_results_df, sim_stats_report, sim_download_csv, sim_download_report]
238
+ )
239
+
240
+ cost_run_button.click(
241
+ fn=run_cost_calculation_interface,
242
+ inputs=[cost_chains_upload, cost_items_upload],
243
+ outputs=[cost_results_df]
244
+ )
245
+
246
+ config_upload.upload(
247
+ fn=lambda x: x.name if x else "",
248
+ inputs=config_upload,
249
+ outputs=config_file_name
250
+ )
251
+
252
+ config_upload.upload(
253
+ fn=lambda file: file.decode('utf-8') if file else "",
254
+ inputs=config_upload,
255
+ outputs=config_content_viewer
256
+ )
257
+
258
+
259
+ if __name__ == "__main__":
260
+ demo.launch()