好久没写碎碎念了,个人来说我不是很喜欢这个标题,有一种敷衍的感觉,让读者不能一眼看出文章主题。但是奈何琐事繁多,感觉有很多乱七八糟的东西想写,还是将本篇文章命名为碎碎念5

YOLO检测

      前不久写了篇文章,详细讲述了用yolov11训练一个无人机检测模型的项目,如果感兴趣,可以去看看
      后来发现离课设截至日期还有点时间,就换了一个更大的模型权重,重新进行一次训练。在上一篇文章里我提到的用yolo11n进行训练,由于权重太小,导致模型的精度不高,检测效果也不理想,而且后来也发现没有嵌入式部署的需求(不需要轻量化),这次就换了个yolo11L,参量是yolo11n的10倍,在相同的训练条件下,检测精度有了大幅度的提高(mAP@0.5达到96%,mAP@0.5:0.95约74%),具体的训练成果已经发布在了github上。

      上图中,左边10张是11L的结果,右边10张是11n的结果。下面是对11L更直观的分析图。

      大概是五月中的时候完成最终的模型训练,然后整个人一下就阳痿了很久,做这个太费精力了😫。后面(直到现在)也没在目标检测方面找到什么有创造性的尝试,于是就搁置了下来(属于是一个11L给我训阳痿了)。
      另外这里贴一个挺好用的Python程序,能将训练完成的模型参数做一个可视化的分析,有时它那个默认的分析图不是很清晰,上面那个彩色的分析图就是由该程序输出的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import pandas as pd
import matplotlib.pyplot as plt

# 读取 CSV 文件
df = pd.read_csv("results.csv")

# 设置图像大小
plt.figure(figsize=(16, 10))

# ---------------------- 图1:Loss 曲线 ----------------------
plt.subplot(2, 2, 1)
plt.plot(df["epoch"], df["train/box_loss"], label="Train Box Loss", color='blue')
plt.plot(df["epoch"], df["val/box_loss"], label="Val Box Loss", linestyle='--', color='blue')
plt.plot(df["epoch"], df["train/cls_loss"], label="Train Cls Loss", color='green')
plt.plot(df["epoch"], df["val/cls_loss"], label="Val Cls Loss", linestyle='--', color='green')
plt.plot(df["epoch"], df["train/dfl_loss"], label="Train DFL Loss", color='red')
plt.plot(df["epoch"], df["val/dfl_loss"], label="Val DFL Loss", linestyle='--', color='red')
plt.title("Box/Cls/DFL Loss")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.grid(True)

# ---------------------- 图2:Precision 曲线 ----------------------
plt.subplot(2, 2, 2)
plt.plot(df["epoch"], df["metrics/precision(B)"], label="Precision", color='orange')
plt.title("Precision over Epochs")
plt.xlabel("Epoch")
plt.ylabel("Precision")
plt.legend()
plt.grid(True)

# ---------------------- 图3:Recall 曲线 ----------------------
plt.subplot(2, 2, 3)
plt.plot(df["epoch"], df["metrics/recall(B)"], label="Recall", color='purple')
plt.title("Recall over Epochs")
plt.xlabel("Epoch")
plt.ylabel("Recall")
plt.legend()
plt.grid(True)

# ---------------------- 图4:mAP 曲线 ----------------------
plt.subplot(2, 2, 4)
plt.plot(df["epoch"], df["metrics/mAP50(B)"], label="mAP@0.5", color='teal')
plt.plot(df["epoch"], df["metrics/mAP50-95(B)"], label="mAP@0.5:0.95", color='brown')
plt.title("mAP over Epochs")
plt.xlabel("Epoch")
plt.ylabel("mAP")
plt.legend()
plt.grid(True)

# 布局优化 & 显示图像
plt.tight_layout()
plt.show()

课设

      由于选课的原因,这学期要做好几个课设(3个左右),然后自己又经常犯懒,很多工作都拖到最后几天才做,好在自己鬼点子多,最后都弄完了。

课设1 课设2 课设3

其中两个课设是独立完成的,对没错,自己就是一个组,然后惊讶的发现,一个人做课设的效率竟然出奇的高,完成度甚至超过了组队的课设任务。这是为什么呢?我觉得可以在之前一篇名为“乌合之众”的文章里找到答案。

比赛

      暑假开始没多久,和学校里的两个研究生学长组队,报名了一堆比赛,乱七八糟的都有,其中一个是2025年中国高校计算机大赛-人工智能创意赛,到现在也没整出什么好活,大致的思路就是多模态(红外,视觉,偏振)——算法去雾——降噪——AI物体检测——AI去雾效果评估,个人感觉没什么创新点。
      一开始感觉限制有点多,比如一定要用百度的那个飞浆studio训练和部署什么的,后来才发现其实是自己创意有点枯竭了,在AI训练-检测方面想不出什么新花样,想不出这样的AI还能有什么好玩又实用的应用场景。然后现在这个项目的流程是这样的:用AI生成几张带雾的牧场图,然后用AI去雾,用AI检测牧场中的牛羊,最后用AI评估去雾效果,全部东西都是AI做的,整个东西充满着一种后赛博朋克的离奇与荒诞,明明叫“创意大赛”,却又在某种程度极大的限制了我们的主观能动性。
      下面是一些做的东西和过程:

      本来没什么灵感就烦躁,这■■百度飞浆studio还难用,一直报错,相关的模型到现在我都没训练出来,属于是红温冒油了,百度,看看你干的好事!!!

百度飞浆项目地址 GITHUB项目地址

博客

      好久没有维护博客,有些CDN已经失效,打开控制台一片红色。找时间修复一下。

      另外,文章的AI摘要貌似也挂了,迟点看看是怎么回事。

运动

      暑假回家之后经常运动,基本每天下午跑3到5公里,然后去楼顶拉伸10分钟,最后回家里撸一会铁。隔三岔五去游泳、打羽毛球。

👉点击展开某次跑步记录

最近在调整自己的跑步姿势,更加合理的步幅,更加短的触地时间,更大的垂直步幅比。触地时间越短,跑步的效率就越高,优秀的跑者一般是在220ms左右,我之前都是350ms往上的。但我发现,缩短触地时间意味着需要尽可能前脚掌着地(我一般是整个脚掌着地),有种踮着脚跑的感觉,跑一会小腿就紧的不行,所以还在找提高跑步效率的方法。

创造

摄影

      拍闪电教程🌩️:脚架,手动对焦(无穷远),ISO100,光圈F22,B档。合适的天气:乌云刚刚过境(或者不是太厚、不是太低的乌云,即将到来的台风是最佳选择),最好是接地闪(云间闪不太好拍,角度难以把控),去楼顶架好脚架耐心等待即可。当然了,这一切的前提是足够的热爱和兴趣。
      请注意一定要注意安全,确保闪电与自己之间有相当的距离,我的建议是只有听不见雷声的闪电,才能上楼顶拍摄。当能听见雷声时,请不要到任何露天的高处活动。
      下面是我在25楼楼顶拍摄的闪电:

雷公助我!!!这次运气很好,在楼顶蹲了3个多小时,拍到了这些满意的闪电。

代码

      弄了一些有意思的程序,第一个是宇宙文明模拟器,灵感来源于《三体》中的黑暗森林理论,这里我只进行了相当简单的模拟,来展示宇宙各文明间可能的联系,基本逻辑为:文明总数x,文明编号0-x,每个文明发达程度0-x随机分布,每过x秒每个文明发达程度随机加或减0-x,两个文明间发现的概率x,更加发达的文明消灭低级文明概率为x,低级文明消灭高级文明的概率为x,两个文明融合成一个大文明的概率为x,融合的文明程度在想加的基础上随机加或减0-x,x都是可以在程序里修改的。
      这个程序只定义了8个和文明进程有关的参数,实际上宇宙文明(如果存在的话)远比这复杂的多的多,所以图一乐就好了~~。该程序较长,请酌情展开,或者直接下载文件运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
import tkinter as tk
from tkinter import ttk, scrolledtext
import random
import threading
import time
from dataclasses import dataclass
from typing import List, Dict
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
from datetime import datetime

@dataclass
class Civilization:
id: int
alive: bool
initial_development: int
current_development: int

def __str__(self):
status = "存活" if self.alive else "灭绝"
return f"文明{self.id}: {status}, 初始发达程度: {self.initial_development}, 当前发达程度: {self.current_development}"

@dataclass
class SimulationEvent:
timestamp: str
event_type: str # "extinction", "merge", "development"
description: str
civilizations_involved: List[int]

def __str__(self):
return f"[{self.timestamp}] {self.description}"

class CivilizationSimulator:
def __init__(self, root):
self.root = root
self.root.title("文明模拟器")
self.root.geometry("1200x800")

self.civilizations: List[Civilization] = []
self.events: List[SimulationEvent] = []
self.running = False
self.simulation_thread = None
self.step_counter = 0

# 参数变量
self.num_civilizations = tk.IntVar(value=10)
self.time_interval = tk.DoubleVar(value=1.0)
self.development_change_range = tk.IntVar(value=5)
self.discovery_probability = tk.DoubleVar(value=0.1)
self.advanced_destroy_probability = tk.DoubleVar(value=0.3)
self.primitive_destroy_probability = tk.DoubleVar(value=0.05)
self.merge_probability = tk.DoubleVar(value=0.2)
self.merge_change_range = tk.IntVar(value=3)

self.setup_ui()

def setup_ui(self):
# 主框架
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

# 左侧控制面板
control_frame = ttk.LabelFrame(main_frame, text="控制面板", padding=10)
control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))

# 参数设置
ttk.Label(control_frame, text="文明总数:").pack(anchor=tk.W)
ttk.Spinbox(control_frame, from_=2, to=100, textvariable=self.num_civilizations, width=15).pack(pady=2)

ttk.Label(control_frame, text="时间间隔(秒):").pack(anchor=tk.W, pady=(10,0))
ttk.Spinbox(control_frame, from_=0.1, to=10.0, increment=0.1, textvariable=self.time_interval, width=15).pack(pady=2)

ttk.Label(control_frame, text="发达程度变化范围:").pack(anchor=tk.W, pady=(10,0))
ttk.Spinbox(control_frame, from_=1, to=50, textvariable=self.development_change_range, width=15).pack(pady=2)

ttk.Label(control_frame, text="发现概率:").pack(anchor=tk.W, pady=(10,0))
ttk.Spinbox(control_frame, from_=0.01, to=1.0, increment=0.01, textvariable=self.discovery_probability, width=15).pack(pady=2)

ttk.Label(control_frame, text="高级文明消灭概率:").pack(anchor=tk.W, pady=(10,0))
ttk.Spinbox(control_frame, from_=0.01, to=1.0, increment=0.01, textvariable=self.advanced_destroy_probability, width=15).pack(pady=2)

ttk.Label(control_frame, text="低级文明消灭概率:").pack(anchor=tk.W, pady=(10,0))
ttk.Spinbox(control_frame, from_=0.01, to=1.0, increment=0.01, textvariable=self.primitive_destroy_probability, width=15).pack(pady=2)

ttk.Label(control_frame, text="融合概率:").pack(anchor=tk.W, pady=(10,0))
ttk.Spinbox(control_frame, from_=0.01, to=1.0, increment=0.01, textvariable=self.merge_probability, width=15).pack(pady=2)

ttk.Label(control_frame, text="融合变化范围:").pack(anchor=tk.W, pady=(10,0))
ttk.Spinbox(control_frame, from_=1, to=20, textvariable=self.merge_change_range, width=15).pack(pady=2)

# 控制按钮
ttk.Button(control_frame, text="初始化文明", command=self.initialize_civilizations).pack(pady=(20,5), fill=tk.X)
ttk.Button(control_frame, text="开始模拟", command=self.start_simulation).pack(pady=5, fill=tk.X)
ttk.Button(control_frame, text="停止模拟", command=self.stop_simulation).pack(pady=5, fill=tk.X)
ttk.Button(control_frame, text="重置", command=self.reset_simulation).pack(pady=5, fill=tk.X)

# 右侧显示区域
display_frame = ttk.Frame(main_frame)
display_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

# 创建notebook用于切换显示
self.notebook = ttk.Notebook(display_frame)
self.notebook.pack(fill=tk.BOTH, expand=True)

# 文本显示标签页
text_frame = ttk.Frame(self.notebook)
self.notebook.add(text_frame, text="文明状态")

self.text_output = scrolledtext.ScrolledText(text_frame, wrap=tk.WORD, width=60, height=30)
self.text_output.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

# 事件记录标签页
events_frame = ttk.Frame(self.notebook)
self.notebook.add(events_frame, text="事件记录")

# 事件记录工具栏
events_toolbar = ttk.Frame(events_frame)
events_toolbar.pack(fill=tk.X, padx=10, pady=(10,5))

ttk.Button(events_toolbar, text="清空记录", command=self.clear_events).pack(side=tk.LEFT)
ttk.Button(events_toolbar, text="导出记录", command=self.export_events).pack(side=tk.LEFT, padx=(10,0))

# 事件过滤器
ttk.Label(events_toolbar, text="筛选事件:").pack(side=tk.LEFT, padx=(20,5))
self.event_filter = ttk.Combobox(events_toolbar, values=["全部", "灭绝事件", "融合事件", "发展变化"], width=12)
self.event_filter.set("全部")
self.event_filter.bind("<<ComboboxSelected>>", self.filter_events)
self.event_filter.pack(side=tk.LEFT)

self.events_output = scrolledtext.ScrolledText(events_frame, wrap=tk.WORD, width=60, height=25)
self.events_output.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

# 统计信息标签页
stats_frame = ttk.Frame(self.notebook)
self.notebook.add(stats_frame, text="统计信息")

self.stats_output = scrolledtext.ScrolledText(stats_frame, wrap=tk.WORD, width=60, height=30)
self.stats_output.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

# 图表显示标签页
chart_frame = ttk.Frame(self.notebook)
self.notebook.add(chart_frame, text="发达程度图表")

self.fig = Figure(figsize=(8, 6), dpi=100)
self.ax = self.fig.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.fig, chart_frame)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

# 状态栏
self.status_var = tk.StringVar(value="准备就绪")
ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN).pack(side=tk.BOTTOM, fill=tk.X, pady=(10,0))

def initialize_civilizations(self):
"""初始化文明"""
self.civilizations.clear()
self.events.clear()
self.step_counter = 0
num_civs = self.num_civilizations.get()

for i in range(num_civs):
initial_dev = random.randint(0, num_civs)
civ = Civilization(
id=i,
alive=True,
initial_development=initial_dev,
current_development=initial_dev
)
self.civilizations.append(civ)

# 记录初始化事件
self.add_event("initialization", f"初始化了 {num_civs} 个文明", list(range(num_civs)))

self.update_display()
self.status_var.set(f"已初始化 {num_civs} 个文明")

def start_simulation(self):
"""开始模拟"""
if not self.civilizations:
self.status_var.set("请先初始化文明")
return

if not self.running:
self.running = True
self.simulation_thread = threading.Thread(target=self.simulation_loop, daemon=True)
self.simulation_thread.start()
self.status_var.set("模拟运行中...")

def stop_simulation(self):
"""停止模拟"""
self.running = False
self.status_var.set("模拟已停止")

def reset_simulation(self):
"""重置模拟"""
self.stop_simulation()
self.civilizations.clear()
self.events.clear()
self.step_counter = 0
self.text_output.delete(1.0, tk.END)
self.events_output.delete(1.0, tk.END)
self.stats_output.delete(1.0, tk.END)
self.ax.clear()
self.canvas.draw()
self.status_var.set("已重置")

def simulation_loop(self):
"""模拟主循环"""
while self.running:
self.simulate_step()
time.sleep(self.time_interval.get())

def simulate_step(self):
"""执行一步模拟"""
self.step_counter += 1
alive_civs = [civ for civ in self.civilizations if civ.alive]

if len(alive_civs) <= 1:
self.running = False
winner = alive_civs[0] if alive_civs else None
if winner:
self.add_event("simulation_end", f"模拟结束 - 文明{winner.id} 成为最后的幸存者!", [winner.id])
else:
self.add_event("simulation_end", "模拟结束 - 所有文明都已灭绝", [])
self.root.after(0, lambda: self.status_var.set("模拟结束"))
return

# 记录每步开始
if self.step_counter % 10 == 1: # 每10步记录一次
alive_count = len(alive_civs)
self.add_event("development", f"第{self.step_counter}步: {alive_count}个文明仍在发展", [civ.id for civ in alive_civs])

# 1. 文明发达程度随机变化
development_changes = []
for civ in alive_civs:
old_dev = civ.current_development
change = random.randint(-self.development_change_range.get(), self.development_change_range.get())
civ.current_development = max(0, civ.current_development + change)

# 记录显著的发展变化
if abs(change) >= self.development_change_range.get() // 2:
direction = "大幅提升" if change > 0 else "急剧衰退"
development_changes.append(f"文明{civ.id}{direction}(变化{change:+d})")

if development_changes:
self.add_event("development", f"发展变化: {', '.join(development_changes)}",
[civ.id for civ in alive_civs if abs(civ.current_development - civ.initial_development) >= self.development_change_range.get() // 2])

# 2. 文明间的相互作用
for i, civ1 in enumerate(alive_civs):
for j, civ2 in enumerate(alive_civs):
if i >= j or not civ1.alive or not civ2.alive:
continue

# 检查是否发现对方
if random.random() < self.discovery_probability.get():
self.handle_civilization_encounter(civ1, civ2)

# 更新显示
self.root.after(0, self.update_display)

def handle_civilization_encounter(self, civ1: Civilization, civ2: Civilization):
"""处理文明相遇"""
if not civ1.alive or not civ2.alive:
return

# 确定哪个文明更发达
if civ1.current_development > civ2.current_development:
advanced, primitive = civ1, civ2
elif civ2.current_development > civ1.current_development:
advanced, primitive = civ2, civ1
else:
# 发达程度相同,随机选择
advanced, primitive = random.choice([(civ1, civ2), (civ2, civ1)])

encounter_roll = random.random()

# 融合
if encounter_roll < self.merge_probability.get():
self.merge_civilizations(advanced, primitive)
# 高级文明消灭低级文明
elif encounter_roll < self.merge_probability.get() + self.advanced_destroy_probability.get():
primitive.alive = False
self.add_event("extinction",
f"高级文明{advanced.id}(发达程度{advanced.current_development}) 消灭了低级文明{primitive.id}(发达程度{primitive.current_development})",
[advanced.id, primitive.id])
# 低级文明消灭高级文明(小概率事件)
elif encounter_roll < (self.merge_probability.get() +
self.advanced_destroy_probability.get() +
self.primitive_destroy_probability.get()):
advanced.alive = False
self.add_event("extinction",
f"低级文明{primitive.id}(发达程度{primitive.current_development}) 奇迹般地消灭了高级文明{advanced.id}(发达程度{advanced.current_development})",
[primitive.id, advanced.id])

def merge_civilizations(self, civ1: Civilization, civ2: Civilization):
"""融合两个文明"""
old_dev1, old_dev2 = civ1.current_development, civ2.current_development

# 新的发达程度 = 两者之和 + 随机变化
base_development = civ1.current_development + civ2.current_development
change = random.randint(-self.merge_change_range.get(), self.merge_change_range.get())
new_development = max(0, base_development + change)

# 保留ID较小的文明,更新其属性
if civ1.id < civ2.id:
survivor, absorbed = civ1, civ2
else:
survivor, absorbed = civ2, civ1

survivor.current_development = new_development
absorbed.alive = False

self.add_event("merge",
f"文明{survivor.id}(发达程度{old_dev1}) 和文明{absorbed.id}(发达程度{old_dev2}) 融合成新文明{survivor.id}(发达程度{new_development}, 变化{change:+d})",
[survivor.id, absorbed.id])

def add_event(self, event_type: str, description: str, civilizations_involved: List[int]):
"""添加事件记录"""
timestamp = datetime.now().strftime("%H:%M:%S")
event = SimulationEvent(
timestamp=timestamp,
event_type=event_type,
description=description,
civilizations_involved=civilizations_involved
)
self.events.append(event)

# 实时更新事件显示
def update_events():
self.events_output.insert(tk.END, str(event) + "\n")
self.events_output.see(tk.END)
self.root.after(0, update_events)

def clear_events(self):
"""清空事件记录"""
self.events.clear()
self.events_output.delete(1.0, tk.END)

def export_events(self):
"""导出事件记录"""
filename = f"civilization_events_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write("文明模拟器事件记录\n")
f.write("=" * 50 + "\n\n")
for event in self.events:
f.write(str(event) + "\n")
self.status_var.set(f"事件记录已导出到 {filename}")
except Exception as e:
self.status_var.set(f"导出失败: {str(e)}")

def filter_events(self, event=None):
"""筛选事件显示"""
filter_type = self.event_filter.get()
self.events_output.delete(1.0, tk.END)

for event in self.events:
show_event = False
if filter_type == "全部":
show_event = True
elif filter_type == "灭绝事件" and event.event_type == "extinction":
show_event = True
elif filter_type == "融合事件" and event.event_type == "merge":
show_event = True
elif filter_type == "发展变化" and event.event_type == "development":
show_event = True

if show_event:
self.events_output.insert(tk.END, str(event) + "\n")

def update_statistics(self):
"""更新统计信息"""
self.stats_output.delete(1.0, tk.END)

# 基本统计
total_civs = len(self.civilizations)
alive_civs = [civ for civ in self.civilizations if civ.alive]
dead_civs = [civ for civ in self.civilizations if not civ.alive]

self.stats_output.insert(tk.END, "=== 文明统计 ===\n")
self.stats_output.insert(tk.END, f"总文明数: {total_civs}\n")
self.stats_output.insert(tk.END, f"存活文明数: {len(alive_civs)}\n")
self.stats_output.insert(tk.END, f"灭绝文明数: {len(dead_civs)}\n")
self.stats_output.insert(tk.END, f"存活率: {len(alive_civs)/total_civs*100:.1f}%\n\n")

if alive_civs:
developments = [civ.current_development for civ in alive_civs]
self.stats_output.insert(tk.END, "=== 存活文明发达程度统计 ===\n")
self.stats_output.insert(tk.END, f"平均发达程度: {sum(developments)/len(developments):.1f}\n")
self.stats_output.insert(tk.END, f"最高发达程度: {max(developments)} (文明{alive_civs[developments.index(max(developments))].id})\n")
self.stats_output.insert(tk.END, f"最低发达程度: {min(developments)} (文明{alive_civs[developments.index(min(developments))].id})\n\n")

# 事件统计
extinction_events = [e for e in self.events if e.event_type == "extinction"]
merge_events = [e for e in self.events if e.event_type == "merge"]

self.stats_output.insert(tk.END, "=== 事件统计 ===\n")
self.stats_output.insert(tk.END, f"总事件数: {len(self.events)}\n")
self.stats_output.insert(tk.END, f"灭绝事件: {len(extinction_events)}\n")
self.stats_output.insert(tk.END, f"融合事件: {len(merge_events)}\n")
self.stats_output.insert(tk.END, f"模拟步数: {self.step_counter}\n\n")

# 最活跃的文明
if self.events:
civ_activity = {}
for event in self.events:
for civ_id in event.civilizations_involved:
civ_activity[civ_id] = civ_activity.get(civ_id, 0) + 1

if civ_activity:
most_active = max(civ_activity.items(), key=lambda x: x[1])
self.stats_output.insert(tk.END, f"最活跃文明: 文明{most_active[0]} (参与{most_active[1]}次事件)\n")

# 详细的存活文明信息
if alive_civs:
self.stats_output.insert(tk.END, "\n=== 存活文明详情 ===\n")
for civ in sorted(alive_civs, key=lambda x: x.current_development, reverse=True):
growth = civ.current_development - civ.initial_development
growth_str = f"(成长{growth:+d})" if growth != 0 else "(无变化)"
self.stats_output.insert(tk.END, f"文明{civ.id}: 当前{civ.current_development}, 初始{civ.initial_development} {growth_str}\n")

def update_display(self):
"""更新显示"""
# 更新文本显示
self.text_output.delete(1.0, tk.END)
alive_count = 0

for civ in self.civilizations:
self.text_output.insert(tk.END, str(civ) + "\n")
if civ.alive:
alive_count += 1

self.text_output.insert(tk.END, f"\n存活文明数量: {alive_count}\n")

# 更新图表
self.update_chart()

# 更新统计信息
self.update_statistics()

def update_chart(self):
"""更新图表显示"""
self.ax.clear()

alive_civs = [civ for civ in self.civilizations if civ.alive]
dead_civs = [civ for civ in self.civilizations if not civ.alive]

if alive_civs:
alive_ids = [civ.id for civ in alive_civs]
alive_developments = [civ.current_development for civ in alive_civs]
self.ax.bar(alive_ids, alive_developments, color='green', alpha=0.7, label='存活文明')

if dead_civs:
dead_ids = [civ.id for civ in dead_civs]
dead_developments = [civ.current_development for civ in dead_civs]
self.ax.bar(dead_ids, dead_developments, color='red', alpha=0.7, label='灭绝文明')

self.ax.set_xlabel('文明编号')
self.ax.set_ylabel('发达程度')
self.ax.set_title('文明发达程度分布')
self.ax.legend()
self.ax.grid(True, alpha=0.3)

self.canvas.draw()

def main():
root = tk.Tk()
app = CivilizationSimulator(root)
root.mainloop()

if __name__ == "__main__":
main()

另一个是键盘监控程序,能统计运行时各个键盘按键被按下的次数。但是这是非系统权限级别的(没有用pywinhook,用的是pynupt),不知道为什么,一用这个玩意就容易卡死,我估计跟系统权限限制有关,所以目前不能记录游戏键盘,即在玩游戏时这个程序是不工作的,其他时候基本能正常运行。程序依旧很长,请酌情展开或下载文件运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
import tkinter as tk
from tkinter import ttk, messagebox
import json
import os
from datetime import datetime
import threading
import time
import sys
import webbrowser
import sqlite3
import queue
from pynput import keyboard

class KeyboardTracker:
def __init__(self):
self.running = False
self.start_time = None
self.end_time = None
self.listener = None
# 修改数据库文件路径,使用程序所在目录
self.db_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "keyboard_stats.db")
self.listener_thread = None
# 软件说明网页URL
self.about_url = "https:2am.top"

# 数字键盘映射
self.numpad_mapping = {
96: "小键盘0", 97: "小键盘1", 98: "小键盘2", 99: "小键盘3",
100: "小键盘4", 101: "小键盘5", 102: "小键盘6", 103: "小键盘7",
104: "小键盘8", 105: "小键盘9", 106: "小键盘*", 107: "小键盘+",
108: "小键盘Enter", 109: "小键盘-", 110: "小键盘.", 111: "小键盘/"
}

# 使用队列处理按键事件,减轻主线程负担
self.key_queue = queue.Queue()

# 使用全局计数变量,避免频繁数据库查询
self.total_count = 0

# 批量插入的缓冲区
self.key_buffer = {}
self.buffer_size = 20 # 缓冲区大小
self.last_flush_time = time.time()
self.flush_interval = 5 # 每5秒强制写入一次

# 线程控制标志
self.worker_running = True

# 添加线程锁,避免多线程资源竞争 -- 修复: 先初始化锁,再使用锁
self._buffer_lock = threading.Lock()
self._db_lock = threading.Lock()

# 确保pynput库可用
try:
self.keyboard = keyboard
print("已加载pynput库")
except ImportError:
print("错误:未安装pynput库,请安装:")
print("pip install pynput")
messagebox.showerror("依赖缺失", "未安装必要的库。请安装pynput:\npip install pynput")
sys.exit(1)

# 初始化数据库
self.init_database()

# 加载缓存的总计数
self.load_total_count()

# 创建GUI
self.create_gui()

# 启动按键处理线程
self.start_db_worker()

def init_database(self):
"""初始化SQLite数据库"""
try:
# 确保数据库目录存在
db_dir = os.path.dirname(self.db_file)
if not os.path.exists(db_dir) and db_dir:
os.makedirs(db_dir)

# 连接数据库(避免使用check_same_thread=False,改用锁机制)
self.conn = sqlite3.connect(self.db_file)
self.conn.execute('PRAGMA journal_mode=WAL') # 启用WAL模式提高并发性能
self.conn.execute('PRAGMA synchronous=NORMAL') # 降低同步级别提高性能
self.conn.execute('PRAGMA temp_store=MEMORY') # 使用内存临时表,提高性能

# 创建表
self.conn.execute('''
CREATE TABLE IF NOT EXISTS key_stats (
key_name TEXT PRIMARY KEY,
count INTEGER DEFAULT 0
)
''')

# 创建元数据表
self.conn.execute('''
CREATE TABLE IF NOT EXISTS metadata (
key TEXT PRIMARY KEY,
value TEXT
)
''')

# 创建索引
self.conn.execute('CREATE INDEX IF NOT EXISTS idx_key_name ON key_stats(key_name)')

self.conn.commit()
print(f"成功初始化数据库: {self.db_file}")
except Exception as e:
print(f"初始化数据库时出错: {e}")
messagebox.showerror("数据库错误", f"初始化数据库时出错: {e}\n数据库路径: {self.db_file}")
sys.exit(1)

def get_db_connection(self):
"""获取线程安全的数据库连接"""
with self._db_lock:
return sqlite3.connect(self.db_file)

def load_total_count(self):
"""从数据库加载总按键次数"""
try:
with self._db_lock:
cursor = self.conn.execute('SELECT SUM(count) FROM key_stats')
result = cursor.fetchone()
self.total_count = result[0] if result[0] is not None else 0
except Exception as e:
print(f"加载总计数时出错: {e}")
self.total_count = 0

def start_db_worker(self):
"""启动数据库工作线程,处理队列中的按键事件"""
def worker():
while self.worker_running:
try:
# 获取队列中的按键或等待超时
try:
key_name = self.key_queue.get(timeout=0.5)

# 添加到缓冲区
with self._buffer_lock:
if key_name in self.key_buffer:
self.key_buffer[key_name] += 1
else:
self.key_buffer[key_name] = 1

# 增加总计数
self.total_count += 1

# 标记任务完成
self.key_queue.task_done()
except queue.Empty:
# 队列超时,检查是否需要刷新缓冲区
pass

# 检查是否需要刷新缓冲区
current_time = time.time()
buffer_size = 0
time_elapsed = False

with self._buffer_lock:
buffer_size = len(self.key_buffer)
time_elapsed = (current_time - self.last_flush_time) >= self.flush_interval

if (buffer_size >= self.buffer_size or time_elapsed) and buffer_size > 0:
self.flush_buffer_to_db()
with self._buffer_lock:
self.last_flush_time = current_time

except Exception as e:
print(f"数据库工作线程出错: {e}")
# 避免线程崩溃导致整个程序无响应
time.sleep(0.5)

self.db_worker = threading.Thread(target=worker, daemon=True)
self.db_worker.start()

def flush_buffer_to_db(self):
"""将按键缓冲区写入数据库"""
# 复制缓冲区并清空,减少锁的持有时间
buffer_copy = {}
with self._buffer_lock:
if not self.key_buffer:
return
buffer_copy = self.key_buffer.copy()
self.key_buffer.clear()

if not buffer_copy:
return

try:
# 使用独立连接和事务,避免阻塞其他操作
with self.get_db_connection() as conn:
cursor = conn.cursor()
# 使用事务批量更新
cursor.execute('BEGIN TRANSACTION')

for key_name, count in buffer_copy.items():
# 使用UPSERT语法
cursor.execute('''
INSERT INTO key_stats (key_name, count) VALUES (?, ?)
ON CONFLICT(key_name) DO UPDATE SET count = count + ?
''', (key_name, count, count))

conn.commit()

except Exception as e:
print(f"写入数据库时出错: {e}")
# 如果写入失败,恢复缓冲区
with self._buffer_lock:
for key, count in buffer_copy.items():
if key in self.key_buffer:
self.key_buffer[key] += count
else:
self.key_buffer[key] = count

def on_press(self, key):
"""按键按下时的回调函数 (用于pynput)"""
try:
# 获取按键名称
key_name = self.get_key_name(key)

# 添加到队列,由工作线程处理
if self.running:
self.key_queue.put(key_name)
except Exception as e:
print(f"处理按键时出错: {e}")

def get_key_name(self, key):
"""获取按键的友好名称 (用于pynput)"""
try:
# 处理数字键盘按键
if isinstance(key, keyboard.KeyCode):
# 检查是否为数字键盘按键
vk = key.vk if hasattr(key, 'vk') else None
if vk is not None and vk in self.numpad_mapping:
return self.numpad_mapping[vk]

# 常规键盘字符
if hasattr(key, 'char') and key.char is not None:
return key.char

# 如果只有vk,尝试使用它
if vk is not None:
return f"键码{vk}"

# 处理特殊按键
if isinstance(key, keyboard.Key):
return key.name

# 默认转换
return str(key).replace("'", "")
except:
# 如果无法确定按键名称,返回原始表示
return str(key)

def on_release(self, key):
"""按键释放时的回调函数 (用于pynput)"""
# 如果不再运行,则停止监听
if not self.running:
return False

def start_tracking(self):
"""开始记录按键"""
if not self.running:
self.running = True
self.start_time = datetime.now()
self.status_var.set("状态: 正在记录...")

# 使用pynput
def start_listener():
with keyboard.Listener(
on_press=self.on_press,
on_release=self.on_release) as listener:
self.listener = listener
try:
listener.join()
except Exception as e:
print(f"监听器错误: {e}")

# 确保之前的线程已经结束
if self.listener_thread and self.listener_thread.is_alive():
self.running = False
if hasattr(self, 'listener') and self.listener:
self.listener.stop()
self.listener_thread.join(timeout=1.0)

self.listener_thread = threading.Thread(target=start_listener, daemon=True)
self.listener_thread.start()

# 更新UI
self.start_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.NORMAL)
self.update_stats_display()

def stop_tracking(self):
"""停止记录按键"""
if self.running:
self.running = False
self.end_time = datetime.now()

# 停止pynput监听器
if hasattr(self, 'listener') and self.listener:
try:
self.listener.stop()
except:
pass

# 确保线程正确停止(设置超时避免永久阻塞)
if self.listener_thread and self.listener_thread.is_alive():
try:
self.listener_thread.join(timeout=2.0)
except:
pass

# 强制刷新缓冲区
self.flush_buffer_to_db()

# 更新UI
self.status_var.set("状态: 已停止")
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
self.update_stats_display()

def reset_data(self):
"""重置按键计数数据"""
if messagebox.askyesno("确认重置", "确定要重置所有按键计数数据吗?"):
try:
# 确保停止记录
was_running = self.running
if was_running:
self.stop_tracking()

# 清空内存中的计数
with self._buffer_lock:
self.total_count = 0
self.key_buffer.clear()

# 清空数据库表
with self.get_db_connection() as conn:
conn.execute("DELETE FROM key_stats")
conn.commit()

# 更新UI
self.update_stats_display()
messagebox.showinfo("重置完成", "按键计数数据已重置")

# 如果之前在记录,则重新开始
if was_running:
self.start_tracking()
except Exception as e:
print(f"重置数据时出错: {e}")
messagebox.showerror("重置失败", f"重置数据时出错: {e}")

def get_top_keys(self, limit=100):
"""获取按键点击次数最多的记录"""
try:
# 合并缓冲区中的数据和数据库中的数据
with self._buffer_lock:
buffer_copy = self.key_buffer.copy()

results = {}

# 从数据库获取记录
with self.get_db_connection() as conn:
cursor = conn.execute('''
SELECT key_name, count FROM key_stats
ORDER BY count DESC LIMIT ?
''', (limit,))

for key_name, count in cursor:
# 加上缓冲区中的计数
buffer_count = buffer_copy.pop(key_name, 0)
results[key_name] = count + buffer_count

# 添加只在缓冲区中的键
for key_name, count in buffer_copy.items():
if key_name in results:
results[key_name] += count
else:
results[key_name] = count

# 返回排序后的结果
return sorted(results.items(), key=lambda x: x[1], reverse=True)[:limit]
except Exception as e:
print(f"获取排名前列按键时出错: {e}")
return []

def update_stats_display(self):
"""更新统计显示,使用分页加载减少负担"""
try:
# 使用after_idle确保在主线程中执行UI更新
self.root.after_idle(self._do_update_stats_display)
except Exception as e:
print(f"安排更新统计显示时出错: {e}")

def _do_update_stats_display(self):
"""实际执行UI更新操作(在主线程)"""
try:
# 获取当前选择的行,以便更新后可以保持选择
selected_items = self.tree.selection()
selected_keys = []
for item in selected_items:
item_values = self.tree.item(item, 'values')
if item_values:
selected_keys.append(item_values[0])

# 清空现有项目
for item in self.tree.get_children():
self.tree.delete(item)

# 加载前100个按键
sorted_keys = self.get_top_keys(100)

# 插入数据
for i, (key_name, count) in enumerate(sorted_keys):
self.tree.insert('', i, values=(key_name, count))

# 尝试恢复之前的选择
for key in selected_keys:
for item in self.tree.get_children():
if self.tree.item(item, 'values')[0] == key:
self.tree.selection_add(item)
break

# 更新总计
self.total_var.set(f"总按键次数: {self.total_count}")

# 更新运行时间
if self.start_time:
end = self.end_time if self.end_time else datetime.now()
duration = end - self.start_time
hours, remainder = divmod(duration.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
time_str = f"{hours}小时 {minutes}分钟 {seconds}秒"
self.time_var.set(f"运行时间: {time_str}")
except Exception as e:
print(f"更新统计显示时出错: {e}")

def export_data(self):
"""导出数据到CSV文件"""
try:
# 强制刷新缓冲区
self.flush_buffer_to_db()

filename = f"keyboard_stats_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"

# 获取所有数据
with self.get_db_connection() as conn:
cursor = conn.execute('SELECT key_name, count FROM key_stats ORDER BY count DESC')
rows = cursor.fetchall()

with open(filename, 'w', encoding='utf-8') as f:
f.write("按键,次数\n")
for key_name, count in rows:
f.write(f"{key_name},{count}\n")
messagebox.showinfo("导出成功", f"数据已导出至 {filename}")
except Exception as e:
messagebox.showerror("导出失败", f"导出数据时出错: {e}")

def open_about_page(self):
"""打开关于页面"""
try:
webbrowser.open(self.about_url)
except Exception as e:
messagebox.showerror("打开失败", f"无法打开网页: {e}")

def create_gui(self):
"""创建图形用户界面"""
self.root = tk.Tk()
self.root.title("键盘使用统计")
self.root.geometry("600x500")

# 创建框架
control_frame = ttk.Frame(self.root, padding=10)
control_frame.pack(fill=tk.X)

stats_frame = ttk.Frame(self.root, padding=10)
stats_frame.pack(fill=tk.BOTH, expand=True)

# 控制按钮
self.start_button = ttk.Button(control_frame, text="开始记录", command=self.start_tracking)
self.start_button.pack(side=tk.LEFT, padx=5)

self.stop_button = ttk.Button(control_frame, text="停止记录", command=self.stop_tracking, state=tk.DISABLED)
self.stop_button.pack(side=tk.LEFT, padx=5)

self.reset_button = ttk.Button(control_frame, text="重置数据", command=self.reset_data)
self.reset_button.pack(side=tk.LEFT, padx=5)

self.export_button = ttk.Button(control_frame, text="导出CSV", command=self.export_data)
self.export_button.pack(side=tk.LEFT, padx=5)

# 添加关于按钮
self.about_button = ttk.Button(control_frame, text="关于", command=self.open_about_page)
self.about_button.pack(side=tk.LEFT, padx=5)

# 状态显示
status_frame = ttk.Frame(self.root, padding=5)
status_frame.pack(fill=tk.X)

self.status_var = tk.StringVar(value="状态: 就绪")
status_label = ttk.Label(status_frame, textvariable=self.status_var)
status_label.pack(side=tk.LEFT, padx=5)

self.total_var = tk.StringVar(value="总按键次数: 0")
total_label = ttk.Label(status_frame, textvariable=self.total_var)
total_label.pack(side=tk.LEFT, padx=15)

self.time_var = tk.StringVar(value="运行时间: 0小时 0分钟 0秒")
time_label = ttk.Label(status_frame, textvariable=self.time_var)
time_label.pack(side=tk.LEFT, padx=5)

# 创建表格显示
columns = ('key', 'count')
self.tree = ttk.Treeview(stats_frame, columns=columns, show='headings')
self.tree.heading('key', text='按键')
self.tree.heading('count', text='次数')
self.tree.column('key', width=100)
self.tree.column('count', width=80)
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

# 添加滚动条
scrollbar = ttk.Scrollbar(stats_frame, orient=tk.VERTICAL, command=self.tree.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.tree.configure(yscrollcommand=scrollbar.set)

# 添加库信息标签
lib_text = "使用pynput库 (可能无法捕获全屏游戏按键)"
lib_label = ttk.Label(self.root, text=lib_text, foreground="gray")
lib_label.pack(side=tk.BOTTOM, pady=5)

# 添加数据库信息标签
db_label = ttk.Label(self.root, text=f"使用SQLite数据库存储: {self.db_file}", foreground="gray")
db_label.pack(side=tk.BOTTOM, pady=0)

# 加载并显示现有数据
self.update_stats_display()

# 当窗口关闭时保存数据并停止记录
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

# 初始化定时更新计数器
self._update_counter = 0

def on_closing(self):
"""窗口关闭时的处理函数"""
# 停止按键记录
if self.running:
self.stop_tracking()

# 停止工作线程
self.worker_running = False

# 刷新并等待队列清空
try:
self.flush_buffer_to_db()
# 等待队列处理完成
if hasattr(self, 'key_queue') and self.key_queue:
try:
self.key_queue.join(timeout=2.0)
except:
pass
except:
pass

# 等待工作线程结束
if hasattr(self, 'db_worker') and self.db_worker and self.db_worker.is_alive():
try:
self.db_worker.join(timeout=2.0)
except:
pass

# 关闭数据库
try:
if hasattr(self, 'conn') and self.conn:
self.conn.close()
except:
pass

# 销毁窗口
self.root.destroy()

def periodic_update(self):
"""定期更新UI"""
try:
if not hasattr(self, 'root') or not self.root:
return # 如果窗口已经关闭,则不再继续

if self.running:
# 更新运行时间和总计数,这是比较轻量的操作
if self.start_time:
end = datetime.now()
duration = end - self.start_time
hours, remainder = divmod(duration.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
time_str = f"{hours}小时 {minutes}分钟 {seconds}秒"
self.time_var.set(f"运行时间: {time_str}")

# 每10次更新才执行完整的统计更新,减少UI负担
self._update_counter += 1
if self._update_counter >= 10:
self._update_counter = 0
self.update_stats_display()
else:
# 仅更新总计数
self.total_var.set(f"总按键次数: {self.total_count}")

# 重新安排下一次更新
self.root.after(2000, self.periodic_update)
except Exception as e:
print(f"定期更新UI时出错: {e}")
# 发生错误时也要重新安排,确保更新不会中断
self.root.after(2000, self.periodic_update)

def run(self):
"""启动应用程序主循环"""
# 开始定期更新UI
self.root.after(2000, self.periodic_update)

# 如果配置了自动开始,则启动记录
if self.get_auto_start():
self.start_tracking()

# 启动主循环
self.root.mainloop()

def get_auto_start(self):
"""获取自动启动设置"""
try:
with self._db_lock:
cursor = self.conn.execute('SELECT value FROM metadata WHERE key = "auto_start"')
result = cursor.fetchone()
if result:
return result[0].lower() == 'true'
return False
except Exception as e:
print(f"获取自动启动设置时出错: {e}")
return False

def set_auto_start(self, auto_start):
"""设置自动启动"""
try:
value = 'true' if auto_start else 'false'
with self._db_lock:
self.conn.execute('''
INSERT INTO metadata (key, value) VALUES (?, ?)
ON CONFLICT(key) DO UPDATE SET value = ?
''', ('auto_start', value, value))
self.conn.commit()
return True
except Exception as e:
print(f"设置自动启动时出错: {e}")
return False

def main():
"""主函数"""
try:
# 创建并运行键盘跟踪器
tracker = KeyboardTracker()
tracker.run()
except Exception as e:
print(f"程序运行时发生错误: {e}")
messagebox.showerror("程序错误", f"程序运行时发生错误: {e}")
sys.exit(1)

if __name__ == "__main__":
main()

BLENDER

      最近研究blender的光影非常上头,发现一种用节点的办法,能让光线也有材质,在加上一点密度很低的体积,看起来有耶稣光的感觉,很美妙。

关于愤青

      有时想想,自己算不算个愤青?说算也算,不算也不算。愤青的定义(普遍来讲)是“对社会现象有强烈的情绪反应,甚至会因此而愤怒”,我当然也对很多当下的现象感到不满,却很少感到愤怒,更多的是好奇这背后的原因是什么,是什么导致了这样的现象(虽说想清楚了也没什么用),思考的过程能带来很多乐趣。
      或者也可以分析一下为什么愤青会“愤怒”,我觉得很大一部分原因是对于现状的无可奈何,对无力改变现状的一种情绪发泄,造成了愤怒的情感。我对于自己的能力那是相当的清楚,知道自己从来就不是一个改变者,而是一个记录者思考者,就像我之前在《数字时代》那篇文章里说的一样,我们都是海里的鱼,抱怨海水是咸的没有任何意义,也不可能去改变这一事实,对自己有了清晰的定位,就不会经常感到无谓的愤怒了。
      但是另一方面,能成为愤青恰恰又说明,一个人对未来是充满希望的,对生活是充满热情的,对于自己对世界的改变有着满腔的热血,这是一种很积极的心态,也很符合年轻人应该有的气质。并且这与上面的论述并不矛盾——能 “对无力改变现状的一种情绪发泄” 的前提是,一个人得先有改变现状的想法。所以我的建议是,成为一个“适当的”愤青,既不被愤怒冲昏了头脑,也不会失去对生活的信心,若能在这两者之间找到平衡,实乃人生一大美事也。