|
1 | 1 | import { Tree } from "./tree.js";
|
2 | 2 | import { Node, NodeAim } from "./node.js";
|
3 | 3 | import { EvaluateNodeFunc } from "./interfaces.js";
|
4 |
| -import { SearchExit, SearchMethod, SearchOpts, SearchResult } from "./search.js"; |
| 4 | +import { RemovalMethod, SearchExit, SearchMethod, SearchOpts, SearchResult } from "./search.js"; |
5 | 5 | import { bubbleSort, bubbleSortEfficient, defaultSort, SortMethod } from "./sorting.js";
|
6 | 6 |
|
7 | 7 | /**
|
@@ -73,6 +73,17 @@ export class SearchTree<GS, M, D> extends Tree<GS, M, D> {
|
73 | 73 | return result;
|
74 | 74 | } else {
|
75 | 75 | prevResult = result;
|
| 76 | + // Check if node removal is enabled |
| 77 | + if (this.opts.removalMethod != RemovalMethod.NONE) { |
| 78 | + if ( |
| 79 | + this.opts.removalMethod == RemovalMethod.ALWAYS || |
| 80 | + (this.opts.removalMethod == RemovalMethod.DEPTH && activeDepth >= this.opts.removalDepth) || |
| 81 | + (this.opts.removalMethod == RemovalMethod.COUNT && this.nodeCount >= this.opts.removalCount) |
| 82 | + ) { |
| 83 | + // Remove nodes to minimum required for next search depth |
| 84 | + this.removeNodes(); |
| 85 | + } |
| 86 | + } |
76 | 87 | }
|
77 | 88 | }
|
78 | 89 | }
|
@@ -299,4 +310,53 @@ export class SearchTree<GS, M, D> extends Tree<GS, M, D> {
|
299 | 310 | getOptimalMoves(): M[] {
|
300 | 311 | return [...this.optimalMoveGen(this.activeRoot)];
|
301 | 312 | }
|
| 313 | + |
| 314 | + removeNodes(): number { |
| 315 | + const removedCount = this.removeNonBestNodes(this.activeRoot, 0, true); |
| 316 | + this.nodeCount -= removedCount; |
| 317 | + return removedCount; |
| 318 | + } |
| 319 | + |
| 320 | + /** |
| 321 | + * Recursively go through node and its children removing all |
| 322 | + * nodes that are not best |
| 323 | + */ |
| 324 | + protected removeNonBestNodes(node: Node<GS, M, D>, depth: number, keep: boolean): number { |
| 325 | + // Check if node has 0 children |
| 326 | + if (node.children.length == 0) { |
| 327 | + return 0; |
| 328 | + } else if (keep) { |
| 329 | + // keep all children, remove grand-children etc |
| 330 | + let removedCount = 0; |
| 331 | + // Iterate through children |
| 332 | + for (let i = 0; i < node.children.length; i++) { |
| 333 | + const child = node.children[i]; |
| 334 | + removedCount += this.removeNonBestNodes(child, depth + 1, keep && i == 0); |
| 335 | + } |
| 336 | + node.descendantCount -= removedCount; |
| 337 | + return removedCount; |
| 338 | + } else { |
| 339 | + // Only keep the best child |
| 340 | + // Sort current children |
| 341 | + bubbleSort(node.children, false, false); |
| 342 | + // Check that best child is correct |
| 343 | + if (node.children[0] != node.child) { |
| 344 | + throw new Error("Node best child mismatch"); |
| 345 | + } |
| 346 | + // Update moves and get number of nodes removed |
| 347 | + let removedCount = -node.children[0].descendantCount - 1; |
| 348 | + for (let i = 0; i < node.children.length; i++) { |
| 349 | + node.moves[i] = node.children[i].move; |
| 350 | + removedCount += node.children[i].descendantCount + 1; |
| 351 | + } |
| 352 | + // Remove the nodes |
| 353 | + node.children = [node.children[0]]; |
| 354 | + // Recusrive call on remaining child |
| 355 | + removedCount += this.removeNonBestNodes(node.children[0], depth + 1, false); |
| 356 | + // Update counts and index of next move to generate from |
| 357 | + node.descendantCount -= removedCount; |
| 358 | + node.moveInd = 1; |
| 359 | + return removedCount; |
| 360 | + } |
| 361 | + } |
302 | 362 | }
|
0 commit comments