deeper-cli 1.2.5 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deeper-cli",
3
- "version": "1.2.5",
3
+ "version": "1.3.1",
4
4
  "description": "DeeperCode - 一句话生成完整项目的 AI Agentic CLI 工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -7,14 +7,14 @@ export interface MemoryEntry {
7
7
  type: 'working' | 'episodic' | 'semantic' | 'procedural';
8
8
  content: string;
9
9
  tags: string[];
10
- importance: number; // 0-10
11
- accuracy: number; // 0-10, 自评置信度
10
+ importance: number;
11
+ accuracy: number;
12
12
  createdAt: number;
13
13
  accessedAt: number;
14
14
  accessCount: number;
15
15
  sessionId: string;
16
- source: string; // 'user' | 'agent' | 'tool' | 'system'
17
- references: string[]; // 关联条目 ID
16
+ source: string;
17
+ references: string[];
18
18
  }
19
19
 
20
20
  interface XMemStats {
@@ -22,13 +22,17 @@ interface XMemStats {
22
22
  byType: Record<string, number>;
23
23
  totalTokens: number;
24
24
  lastCleanup: number;
25
+ lastConsolidate: number;
25
26
  }
26
27
 
27
28
  const MEM_DIR = join(DEEPER_HOME, 'xmemory');
28
29
  const STATS_FILE = join(MEM_DIR, 'stats.json');
29
- const MAX_WORKING_MEM = 50;
30
- const MAX_TOTAL_MEM = 2000;
31
- const CLEANUP_THRESHOLD = 1500;
30
+ const MAX_WORKING_MEM = 100;
31
+ const MAX_TOTAL_MEM = 3000;
32
+ const CLEANUP_THRESHOLD = 2000;
33
+ const DEDUP_SIMILARITY_THRESHOLD = 0.85;
34
+ const CONSOLIDATE_THRESHOLD = 5;
35
+ const SAVE_DEBOUNCE_MS = 3000;
32
36
 
33
37
  let currentSessionId = '';
34
38
 
@@ -41,30 +45,87 @@ function uid(): string {
41
45
  function ensureDir(): void {
42
46
  if (!existsSync(MEM_DIR)) mkdirSync(MEM_DIR, { recursive: true });
43
47
  if (!existsSync(STATS_FILE)) {
44
- writeFileSync(STATS_FILE, JSON.stringify({ totalEntries: 0, byType: {}, totalTokens: 0, lastCleanup: Date.now() }), 'utf-8');
48
+ writeFileSync(STATS_FILE, JSON.stringify({ totalEntries: 0, byType: {}, totalTokens: 0, lastCleanup: Date.now(), lastConsolidate: Date.now() }, null, 2), 'utf-8');
45
49
  }
46
50
  }
47
51
 
48
52
  function loadStats(): XMemStats {
49
53
  try {
50
54
  return JSON.parse(readFileSync(STATS_FILE, 'utf-8')) as XMemStats;
51
- } catch { return { totalEntries: 0, byType: {}, totalTokens: 0, lastCleanup: 0 }; }
55
+ } catch { return { totalEntries: 0, byType: {}, totalTokens: 0, lastCleanup: 0, lastConsolidate: 0 }; }
52
56
  }
53
57
 
54
58
  function saveStats(s: XMemStats): void {
55
59
  writeFileSync(STATS_FILE, JSON.stringify(s, null, 2), 'utf-8');
56
60
  }
57
61
 
62
+ function tokenize(text: string): string[] {
63
+ const lower = text.toLowerCase();
64
+ const words = lower.split(/[\s\p{P}\p{S}]+/).filter(w => w.length >= 2);
65
+ const bigrams: string[] = [];
66
+ for (let i = 0; i < words.length - 1; i++) bigrams.push(words[i] + '_' + words[i + 1]);
67
+ return [...new Set([...words, ...bigrams])];
68
+ }
69
+
70
+ function similarity(a: string, b: string): number {
71
+ if (a === b) return 1;
72
+ if (!a || !b) return 0;
73
+ const ta = tokenize(a);
74
+ const tb = tokenize(b);
75
+ if (ta.length === 0 || tb.length === 0) return 0;
76
+ const sa = new Set(ta);
77
+ const sb = new Set(tb);
78
+ let intersection = 0;
79
+ for (const t of sa) if (sb.has(t)) intersection++;
80
+ const union = Math.max(sa.size, sb.size);
81
+ return union > 0 ? intersection / union : 0;
82
+ }
83
+
84
+ class TFIDFIndexer {
85
+ private docFreq: Map<string, number> = new Map();
86
+ private numDocs = 0;
87
+
88
+ build(entries: Iterable<MemoryEntry>): void {
89
+ this.docFreq.clear();
90
+ this.numDocs = 0;
91
+ for (const entry of entries) {
92
+ this.numDocs++;
93
+ const tokens = new Set(tokenize(entry.content));
94
+ for (const t of tokens) this.docFreq.set(t, (this.docFreq.get(t) || 0) + 1);
95
+ }
96
+ }
97
+
98
+ idf(term: string): number {
99
+ const df = this.docFreq.get(term) || 0;
100
+ if (df === 0 || df >= this.numDocs) return 0;
101
+ return Math.log((this.numDocs + 0.5) / (df + 0.5)) + 1;
102
+ }
103
+
104
+ tfidfScore(content: string, queryTerms: string[]): number {
105
+ const tokens = tokenize(content);
106
+ const tfMap = new Map<string, number>();
107
+ for (const t of tokens) tfMap.set(t, (tfMap.get(t) || 0) + 1);
108
+ const maxTf = Math.max(...tfMap.values(), 1);
109
+ let score = 0;
110
+ for (const qt of queryTerms) {
111
+ const tf = ((tfMap.get(qt) || 0) / maxTf) * (1 + Math.log(tokens.length));
112
+ score += tf * this.idf(qt);
113
+ }
114
+ return score;
115
+ }
116
+ }
117
+
58
118
  export class XMemory {
59
119
  private working: MemoryEntry[] = [];
60
120
  private index: Map<string, MemoryEntry> = new Map();
61
121
  private dirty = false;
122
+ private saveTimer: ReturnType<typeof setTimeout> | null = null;
123
+ private tfidf: TFIDFIndexer = new TFIDFIndexer();
62
124
 
63
125
  constructor() {
64
126
  ensureDir();
65
127
  }
66
128
 
67
- // ============ 写入 ============
68
129
  store(
69
130
  type: MemoryEntry['type'],
70
131
  content: string,
@@ -74,9 +135,12 @@ export class XMemory {
74
135
  source: MemoryEntry['source'] = 'agent',
75
136
  references: string[] = [],
76
137
  ): string {
138
+ const deduped = this.deduplicate(type, content, importance);
139
+ if (deduped !== null) return deduped;
140
+
77
141
  const id = uid();
78
142
  const entry: MemoryEntry = {
79
- id, type, content: content.slice(0, 2000), tags,
143
+ id, type, content: content.slice(0, 4000), tags,
80
144
  importance: Math.min(10, Math.max(0, importance)),
81
145
  accuracy: Math.min(10, Math.max(0, accuracy)),
82
146
  createdAt: Date.now(), accessedAt: Date.now(),
@@ -86,13 +150,12 @@ export class XMemory {
86
150
 
87
151
  if (type === 'working') {
88
152
  this.working.push(entry);
89
- if (this.working.length > MAX_WORKING_MEM) {
90
- this.working.shift();
91
- }
153
+ this.evictWorking();
92
154
  }
93
155
 
94
156
  this.index.set(id, entry);
95
157
  this.dirty = true;
158
+ this.scheduleSave();
96
159
 
97
160
  if (this.index.size > CLEANUP_THRESHOLD) {
98
161
  this.autoCleanup();
@@ -117,38 +180,54 @@ export class XMemory {
117
180
  return this.store('working', content, tags, 3, 5, 'agent');
118
181
  }
119
182
 
120
- // ============ 检索 ============
121
183
  recall(query: string, limit = 5, minImportance = 0): MemoryEntry[] {
122
- const keywords = query.toLowerCase().split(/[\s,,。]+/).filter(w => w.length > 1);
184
+ const keywords = tokenize(query).filter(w => w.length >= 2);
123
185
  if (keywords.length === 0) return [];
124
186
 
125
- const scored: Array<{ entry: MemoryEntry; score: number }> = [];
187
+ this.tfidf.build(this.index.values());
126
188
  const now = Date.now();
189
+ const scored: Array<{ entry: MemoryEntry; score: number }> = [];
127
190
 
128
191
  for (const entry of this.index.values()) {
129
192
  if (entry.importance < minImportance) continue;
130
- let score = 0;
131
- const c = (entry.content || '').toLowerCase();
193
+
194
+ const c = entry.content.toLowerCase();
132
195
  const t = entry.tags.join(' ').toLowerCase();
133
196
 
197
+ let keywordScore = 0;
198
+ let exactMatchBonus = 0;
134
199
  for (const kw of keywords) {
135
- if (c.includes(kw)) score += 3;
136
- if (t.includes(kw)) score += 2;
137
- if (entry.type === 'procedural' && c.includes(kw)) score += 1;
200
+ if (c.includes(kw)) { keywordScore += 3; exactMatchBonus += 1; }
201
+ if (t.includes(kw)) keywordScore += 2;
202
+ if (entry.type === 'procedural' && c.includes(kw)) keywordScore += 1;
203
+ if (entry.type === 'semantic' && c.includes(kw)) keywordScore += 1.5;
138
204
  }
139
205
 
140
- // 衰减: 重要度高 + 最近访问 权重高
141
- const age = (now - entry.accessedAt) / (1000 * 60 * 60);
142
- score += entry.importance * 0.5;
143
- score -= Math.min(age / 24, 5); // 每 24 小时衰减 max 5
206
+ const tfidfScore = this.tfidf.tfidfScore(entry.content, keywords);
207
+
208
+ const recencyBoost = Math.max(0, 3 - (now - entry.accessedAt) / (1000 * 60 * 30));
209
+ const frequencyBoost = Math.min(entry.accessCount * 0.3, 4);
210
+ const importanceWeight = entry.importance * 0.6;
211
+ const ageHours = (now - entry.createdAt) / (1000 * 60 * 60);
212
+ const ageDecay = Math.min(ageHours / 48, 6);
144
213
 
145
- if (score > 0) scored.push({ entry, score });
214
+ const totalScore =
215
+ keywordScore * 1.0 +
216
+ tfidfScore * 2.0 +
217
+ importanceWeight +
218
+ recencyBoost +
219
+ frequencyBoost -
220
+ ageDecay +
221
+ (exactMatchBonus >= 3 ? 3 : 0);
222
+
223
+ if (totalScore > 0) scored.push({ entry, score: totalScore });
146
224
  }
147
225
 
148
226
  scored.sort((a, b) => b.score - a.score);
149
227
  return scored.slice(0, limit).map(s => {
150
228
  s.entry.accessedAt = now;
151
229
  s.entry.accessCount++;
230
+ s.entry.importance = Math.min(10, s.entry.importance + 0.1);
152
231
  return s.entry;
153
232
  });
154
233
  }
@@ -158,20 +237,23 @@ export class XMemory {
158
237
  for (const entry of this.index.values()) {
159
238
  if (entry.type === type) res.push(entry);
160
239
  }
161
- res.sort((a, b) => b.createdAt - a.createdAt);
240
+ res.sort((a, b) => (b.importance * 2 + b.accessCount) - (a.importance * 2 + a.accessCount));
162
241
  return res.slice(0, limit);
163
242
  }
164
243
 
165
244
  getWorking(): MemoryEntry[] {
166
- return this.working;
245
+ return this.working.sort((a, b) =>
246
+ (b.importance * 2 + b.accessCount) - (a.importance * 2 + a.accessCount)
247
+ );
167
248
  }
168
249
 
169
- getWorkingContext(maxTokens = 2000): string {
250
+ getWorkingContext(maxTokens = 4000): string {
170
251
  if (this.working.length === 0) return '';
171
- const lines = this.working.map(e => `[工作记忆] ${e.content.slice(0, 300)}`);
252
+ const sorted = this.getWorking();
253
+ const lines = sorted.map(e => `[${e.source}] ${e.content.slice(0, 500)}`);
172
254
  let result = '';
173
255
  for (const line of lines) {
174
- if ((result + line).length > maxTokens * 4) break;
256
+ if ((result + line).length > maxTokens * 3) break;
175
257
  result += line + '\n';
176
258
  }
177
259
  return result;
@@ -181,28 +263,51 @@ export class XMemory {
181
263
  const recalled = this.recall(task, limit, 3);
182
264
  const proc = recalled.filter(r => r.type === 'procedural' || r.type === 'semantic');
183
265
  if (proc.length === 0) return '';
184
- return '[记忆提示]\n' + proc.map(p => `- ${p.content.slice(0, 250)}`).join('\n');
266
+ return '[记忆提示]\n' + proc.map(p => `- [${p.type === 'procedural' ? '技能' : '知识'}] ${p.content.slice(0, 350)}`).join('\n');
185
267
  }
186
268
 
187
269
  getSessionSummary(): string {
188
270
  const entries = [...this.index.values()].filter(e => e.sessionId === currentSessionId);
189
271
  if (entries.length === 0) return '';
190
- const key = entries
191
- .filter(e => e.importance >= 5)
272
+
273
+ const byType: Record<string, MemoryEntry[]> = { semantic: [], procedural: [], episodic: [], working: [] };
274
+ for (const e of entries) {
275
+ if (byType[e.type]) byType[e.type].push(e);
276
+ }
277
+
278
+ const parts: string[] = [];
279
+
280
+ const keySemantic = byType.semantic
281
+ .sort((a, b) => b.importance - a.importance)
282
+ .slice(0, 5);
283
+ if (keySemantic.length > 0) {
284
+ parts.push(`[知识记忆·${keySemantic.length}条]\n` +
285
+ keySemantic.map(e => `• ${e.content.slice(0, 250)}`).join('\n'));
286
+ }
287
+
288
+ const keyProcedural = byType.procedural
289
+ .sort((a, b) => b.importance - a.importance)
290
+ .slice(0, 5);
291
+ if (keyProcedural.length > 0) {
292
+ parts.push(`[技能记忆·${keyProcedural.length}条]\n` +
293
+ keyProcedural.map(e => `• ${e.content.slice(0, 250)}`).join('\n'));
294
+ }
295
+
296
+ const keyEpisodic = byType.episodic
192
297
  .sort((a, b) => b.importance - a.importance)
193
- .slice(0, 8);
194
- return `[XMemory·本会话]\n` + key.map(e => {
195
- const t = e.type === 'semantic' ? '知识' : e.type === 'procedural' ? '技能' : e.type === 'episodic' ? '经历' : '工作';
196
- return `[${t}] ${e.content.slice(0, 200)}`;
197
- }).join('\n');
298
+ .slice(0, 3);
299
+ if (keyEpisodic.length > 0) {
300
+ parts.push(`[经历记忆·${keyEpisodic.length}条]\n` +
301
+ keyEpisodic.map(e => `→ ${e.content.slice(0, 150)}`).join('\n'));
302
+ }
303
+
304
+ return `[XMemory·本会话 ${entries.length}条]\n${parts.join('\n')}`;
198
305
  }
199
306
 
200
- // ============ 持久化 ============
201
307
  async save(): Promise<void> {
202
308
  if (!this.dirty) return;
203
309
  ensureDir();
204
310
 
205
- // 分批写入:每个 type 一个文件
206
311
  const byType: Record<string, MemoryEntry[]> = {};
207
312
  for (const entry of this.index.values()) {
208
313
  if (!byType[entry.type]) byType[entry.type] = [];
@@ -211,7 +316,7 @@ export class XMemory {
211
316
 
212
317
  for (const [type, entries] of Object.entries(byType)) {
213
318
  const file = join(MEM_DIR, `${type}.json`);
214
- writeFileSync(file, JSON.stringify(entries.slice(-500)), 'utf-8'); // 每个类型最多 500 条
319
+ writeFileSync(file, JSON.stringify(entries.slice(-800)), 'utf-8');
215
320
  }
216
321
 
217
322
  const stats = loadStats();
@@ -241,24 +346,125 @@ export class XMemory {
241
346
  if (entry.type === 'working') this.working.push(entry);
242
347
  total++;
243
348
  }
244
- } catch { /* skip corrupt files */ }
349
+ } catch {}
245
350
  }
246
351
 
247
352
  if (this.working.length > MAX_WORKING_MEM) {
248
353
  this.working = this.working.slice(-MAX_WORKING_MEM);
249
354
  }
355
+
356
+ this.consolidateIfNeeded();
357
+ }
358
+
359
+ private scheduleSave(): void {
360
+ if (this.saveTimer) return;
361
+ this.saveTimer = setTimeout(() => {
362
+ this.saveTimer = null;
363
+ this.save().catch(() => {});
364
+ }, SAVE_DEBOUNCE_MS);
365
+ }
366
+
367
+ private deduplicate(type: MemoryEntry['type'], content: string, importance: number): string | null {
368
+ const threshold = type === 'working' ? 0.95 : type === 'episodic' ? 0.85 : DEDUP_SIMILARITY_THRESHOLD;
369
+ const shortContent = content.slice(0, 500);
370
+
371
+ for (const entry of this.index.values()) {
372
+ if (entry.type !== type) continue;
373
+ const sim = similarity(shortContent, entry.content.slice(0, 500));
374
+ if (sim >= threshold) {
375
+ entry.accessedAt = Date.now();
376
+ entry.accessCount++;
377
+ entry.importance = Math.min(10, Math.max(entry.importance, importance));
378
+ if (content.length > entry.content.length && content.length <= 4000) {
379
+ entry.content = content;
380
+ this.dirty = true;
381
+ }
382
+ return entry.id;
383
+ }
384
+ }
385
+ return null;
386
+ }
387
+
388
+ private evictWorking(): void {
389
+ while (this.working.length > MAX_WORKING_MEM) {
390
+ let worstIdx = 0;
391
+ let worstScore = Infinity;
392
+ for (let i = 0; i < this.working.length; i++) {
393
+ const e = this.working[i];
394
+ const score = e.importance + e.accessCount * 0.3 - (Date.now() - e.accessedAt) / (1000 * 60 * 15);
395
+ if (score < worstScore) { worstScore = score; worstIdx = i; }
396
+ }
397
+ this.working.splice(worstIdx, 1);
398
+ }
399
+ }
400
+
401
+ private consolidateIfNeeded(): void {
402
+ const stats = loadStats();
403
+ const now = Date.now();
404
+ if (now - stats.lastConsolidate < 24 * 60 * 60 * 1000) return;
405
+
406
+ const candidates: Map<string, MemoryEntry[]> = new Map();
407
+ for (const entry of this.index.values()) {
408
+ if (entry.type === 'episodic' && entry.accessCount >= CONSOLIDATE_THRESHOLD) {
409
+ const key = entry.tags.slice(0, 3).sort().join('|') || '_default_';
410
+ if (!candidates.has(key)) candidates.set(key, []);
411
+ candidates.get(key)!.push(entry);
412
+ }
413
+ }
414
+
415
+ let consolidated = 0;
416
+ for (const [, group] of candidates) {
417
+ if (group.length < 2) continue;
418
+ group.sort((a, b) => b.accessCount - a.accessCount);
419
+ const best = group[0];
420
+
421
+ const allContent = group.map(g => g.content).join('\n');
422
+ const summary = `[ consolidated from ${group.length} memories ]\n${allContent.slice(0, 2000)}`;
423
+
424
+ const newType = best.accessCount >= 10 ? 'procedural' : 'semantic';
425
+ const existing = this.findSimilar(newType, best.content);
426
+ if (existing) {
427
+ existing.content = summary.slice(0, 4000);
428
+ existing.accessCount += Math.floor(group.reduce((s, g) => s + g.accessCount, 0) / group.length);
429
+ existing.importance = Math.min(10, existing.importance + 1);
430
+ } else {
431
+ this.store(newType, summary, best.tags, Math.min(10, best.importance + 2), 9, 'system');
432
+ consolidated++;
433
+ }
434
+
435
+ for (const old of group) {
436
+ if (old.id !== best.id) this.index.delete(old.id);
437
+ }
438
+ }
439
+
440
+ if (consolidated > 0 || candidates.size > 0) {
441
+ stats.lastConsolidate = now;
442
+ saveStats(stats);
443
+ this.dirty = true;
444
+ }
445
+ }
446
+
447
+ private findSimilar(type: MemoryEntry['type'], content: string): MemoryEntry | null {
448
+ const threshold = 0.75;
449
+ let best: MemoryEntry | null = null;
450
+ let bestSim = 0;
451
+ for (const entry of this.index.values()) {
452
+ if (entry.type !== type) continue;
453
+ const sim = similarity(content.slice(0, 300), entry.content.slice(0, 300));
454
+ if (sim > bestSim) { bestSim = sim; best = entry; }
455
+ }
456
+ return bestSim >= threshold ? best : null;
250
457
  }
251
458
 
252
- // ============ 维护 ============
253
459
  private autoCleanup(): void {
254
460
  const entries = [...this.index.values()];
255
461
  entries.sort((a, b) => {
256
- const scoreA = a.importance * 2 + a.accessCount - (Date.now() - a.accessedAt) / (1000 * 60 * 60 * 24);
257
- const scoreB = b.importance * 2 + b.accessCount - (Date.now() - b.accessedAt) / (1000 * 60 * 60 * 24);
462
+ const scoreA = a.importance * 2 + a.accessCount + (a.type === 'semantic' ? 3 : a.type === 'procedural' ? 2 : 0) - (Date.now() - a.accessedAt) / (1000 * 60 * 60 * 24);
463
+ const scoreB = b.importance * 2 + b.accessCount + (b.type === 'semantic' ? 3 : b.type === 'procedural' ? 2 : 0) - (Date.now() - b.accessedAt) / (1000 * 60 * 60 * 24);
258
464
  return scoreA - scoreB;
259
465
  });
260
466
 
261
- const toKeep = entries.slice(-500);
467
+ const toKeep = entries.slice(-800);
262
468
  this.index.clear();
263
469
  this.working = [];
264
470
  for (const entry of toKeep) {
@@ -240,7 +240,13 @@ export class DeepSeekClient {
240
240
  body.tool_choice = 'auto';
241
241
  }
242
242
 
243
- return JSON.stringify(body);
243
+ return JSON.stringify({
244
+ ...body,
245
+ messages: (body.messages as Array<Record<string, unknown>>).map(m => {
246
+ if (m.role === 'tool' && !m.name) m.name = 'tool';
247
+ return m;
248
+ }),
249
+ });
244
250
  }
245
251
 
246
252
  private async makeRequest(config: DeepSeekConfig, body: string): Promise<Response> {
@@ -1,62 +0,0 @@
1
- # Chinese Chess AI - 中国象棋智能体
2
-
3
- 基于 Alpha-Beta 搜索 + 自博弈学习的轻量级中国象棋 AI 引擎
4
-
5
- ## 特性
6
- - ✅ 完整的中国象棋规则实现
7
- - ✅ Alpha-Beta 剪枝搜索算法
8
- - ✅ 高级优化:置换表、迭代加深、历史启发、杀手步法
9
- - ✅ 自博弈强化学习系统(CPU友好)
10
- - ✅ 轻量级设计,无需GPU
11
- - ✅ 智能评估函数(棋子价值+位置价值+机动性)
12
- - ✅ 命令行交互界面
13
-
14
- ## 安装依赖
15
- ```bash
16
- pip install -r requirements.txt
17
- ```
18
-
19
- ## 使用方法
20
-
21
- ### 人机对战
22
- ```bash
23
- python main.py --mode vs
24
- ```
25
-
26
- ### AI自训练
27
- ```bash
28
- python main.py --mode train --epochs 100
29
- ```
30
-
31
- ### AI对弈(观看两AI对战)
32
- ```bash
33
- python main.py --mode watch
34
- ```
35
-
36
- ## 项目结构
37
- ```
38
- chinese_chess_ai/
39
- ├── core/ # 核心引擎
40
- │ ├── board.py # 棋盘状态管理
41
- │ ├── pieces.py # 棋子定义与规则
42
- │ ├── move_generator.py # 走法生成器
43
- │ └── evaluator.py # 局面评估器
44
- ├── search/ # 搜索算法
45
- │ ├── alphabeta.py # Alpha-Beta搜索
46
- │ └── optimizations.py # 搜索优化技术
47
- ├── learning/ # 学习系统
48
- │ ├── trainer.py # 自博弈训练器
49
- │ └── experience.py # 经验回放缓冲区
50
- ├── ui/ # 用户界面
51
- │ └── cli.py # 命令行界面
52
- ├── utils/ # 工具函数
53
- │ └── helpers.py # 辅助函数
54
- ├── main.py # 主入口
55
- └── config.py # 配置参数
56
- ```
57
-
58
- ## 技术栈
59
- - Python 3.8+
60
- - 纯NumPy计算(无深度学习框架依赖)
61
- - 经典游戏树搜索算法
62
- - 轻量级强化学习
@@ -1,63 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- 中国象棋AI - 配置参数
4
- """
5
-
6
- # 搜索配置
7
- SEARCH_CONFIG = {
8
- 'max_depth': 4, # 最大搜索深度(默认4层,平衡速度与智能)
9
- 'iterative_deepening': True, # 启用迭代加深
10
- 'use_transposition_table': True, # 使用置换表
11
- 'use_killer_moves': True, # 使用杀手步法
12
- 'use_history_heuristic': True, # 使用历史启发
13
- 'null_move_pruning': True, # 空步裁剪
14
- 'null_move_reduction': 2, # 空步减少深度
15
- 'aspiration_window': 50, # 渴望窗口大小
16
- 'time_limit': 3.0, # 时间限制(秒)
17
- }
18
-
19
- # 评估函数配置
20
- EVALUATOR_CONFIG = {
21
- # 棋子基础价值(分)
22
- 'piece_values': {
23
- 'KING': 10000, # 将/帅
24
- 'ADVISOR': 200, # 士/仕
25
- 'ELEPHANT': 200, # 象/相
26
- 'HORSE': 400, # 马
27
- 'CHARIOT': 900, # 车
28
- 'CANNON': 450, # 炮
29
- 'PAWN': 100, # 兵/卒
30
- },
31
-
32
- # 位置价值表权重
33
- 'position_weight': 0.6,
34
-
35
- # 机动性权重
36
- 'mobility_weight': 0.15,
37
-
38
- # 保护与威胁权重
39
- 'protection_weight': 0.1,
40
-
41
- # 中心控制权重
42
- 'center_control_weight': 0.15,
43
- }
44
-
45
- # 训练配置
46
- TRAINING_CONFIG = {
47
- 'learning_rate': 0.01,
48
- 'epochs': 100,
49
- 'games_per_epoch': 10,
50
- 'experience_replay_size': 10000,
51
- 'batch_size': 32,
52
- 'exploration_rate': 0.3, # 初始探索率
53
- 'exploration_decay': 0.995, # 探索率衰减
54
- 'min_exploration_rate': 0.05, # 最小探索率
55
- 'discount_factor': 0.95, # 折扣因子
56
- }
57
-
58
- # 棋盘配置
59
- BOARD_CONFIG = {
60
- 'rows': 10,
61
- 'cols': 9,
62
- 'red_side': 'bottom', # 红方在下方
63
- }
@@ -1 +0,0 @@
1
- numpy>=1.21.0