Skip to content

Commit 8d9b0dd

Browse files
committed
ShashChess 41.1
New option Anti Dancing Analysis See the READme for more infos
1 parent dd82174 commit 8d9b0dd

22 files changed

Lines changed: 516 additions & 85 deletions

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,15 @@ If enabled, allows the engine to store a move in the queue of ChessDb to be anal
238238

239239
_Default 0, min 0, max 512_ The number of threads doing a full depth analysis (brute force). Useful in analysis of particular hard positions to limit the strong pruning's drawbacks.
240240

241+
### Anti Dancing Analysis
242+
_Boolean, Default: False_
243+
Solves one of the most annoying issues when analyzing with modern chess engines: the tendency to "dance" with pieces (repeating the position once or twice) before executing the actual winning plan. Engines normally do this to push the search horizon further or to burn the opponent's clock in practical play, resulting in unnecessarily long, confusing, and unreadable Principal Variations (PVs).
244+
When this option is set to True, ShashChess will strictly forbid any position repetition when it considers itself to be winning. The engine is forced to immediately discard repetitive maneuvers (scoring them as a draw) and must compute the most direct, linear, and human-readable path to convert the advantage.
245+
246+
Usage Recommendations:
247+
Deep Analysis (ON): Highly recommended when analyzing games, studying openings, or trying to understand the core strategic plan of a position without useless tactical noise.
248+
Match Play & Tournaments (OFF): Must be disabled during actual games or engine-vs-engine matches. In some specific scenarios (like pawn endgames), triangulating to lose a tempo via repetition is mathematically the only way to win. If enabled during a game, the engine might throw away a win to avoid the repetition penalty.
249+
241250
### Variety (checkbox)
242251

243252
Default is Off: no variety. The other values are "Standard" (no elo loss: randomicity in Capablanca zone) and Psychological (randomicity in Caos zones max).
@@ -357,7 +366,7 @@ Defense position/algorithm (the "reversed colors" Tal)
357366

358367
### GoldDigger Mode – The Ultimate Tactical Solver
359368

360-
The **GoldDigger** variant is a specialized compilation designed for one purpose: **maximum tactical depth**. By applying a set of aggressive modifications to the search algorithm, GoldDigger explores significantly deeper variations in critical positions, making it an ideal tool for solving complex tactical puzzles, analyzing sharp openings, or studying sacrificial combinations.
369+
The **GoldDigger** variant of Alexander is a specialized compilation designed for one purpose: **maximum tactical depth**. By applying a set of aggressive modifications to the search algorithm, GoldDigger explores significantly deeper variations in critical positions, making it an ideal tool for solving complex tactical puzzles, analyzing sharp openings, or studying sacrificial combinations.
361370

362371
#### How it works
363372

doc/general/TestingStrategy/.~lock.TestingStrategy.docx#

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/benchmark.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Set-ExecutionPolicy Bypass -Scope Process -Force
2-
$ENGINE = ".\ShashChess41-x86-64-bmi2.exe"
2+
$ENGINE = ".\ShashChess41.1-x86-64-bmi2.exe"
33
$LOGFILE = "benchmark_log.txt"
44
$ITERATIONS = 5
55
$TEMP_OUTPUT_STDOUT = "temp_output_stdout.txt"

src/benchmark_log.txt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
=== Benchmark ShashChess 10/17/2025 01:16:22 ===
2-
========================================Time: 2965 ms, Nodes: 2921411, Nodes/sec: 985298
3-
Time: 3004 ms, Nodes: 2921411, Nodes/sec: 972506
4-
Time: 2984 ms, Nodes: 2921411, Nodes/sec: 979025
5-
Time: 2982 ms, Nodes: 2921411, Nodes/sec: 979681
6-
Time: 2989 ms, Nodes: 2921411, Nodes/sec: 977387
1+
=== Benchmark ShashChess 03/01/2026 21:54:20 ===
2+
========================================Time: 2931 ms, Nodes: 2993639, Nodes/sec: 1021371
3+
Time: 2918 ms, Nodes: 2993639, Nodes/sec: 1025921
4+
Time: 2899 ms, Nodes: 2993639, Nodes/sec: 1032645
5+
Time: 2907 ms, Nodes: 2993639, Nodes/sec: 1029803
6+
Time: 2911 ms, Nodes: 2993639, Nodes/sec: 1028388
77
========================================
88
Media dei risultati su 5 esecuzioni:
9-
Average Total time (ms) : 2985
10-
Average Nodes searched : 2921411
11-
Average Nodes/second : 978779
9+
Average Total time (ms) : 2913
10+
Average Nodes searched : 2993639
11+
Average Nodes/second : 1027626

src/difference.txt

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
diff --git a/src/search.cpp b/src/search.cpp
2+
index 50197f5..eae26de 100644
3+
--- a/src/search.cpp
4+
+++ b/src/search.cpp
5+
@@ -2599,8 +2599,11 @@ template<NodeType nodeType>
6+
Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) {
7+
8+
static_assert(nodeType != Root);
9+
+ //by shashin definitions begin
10+
const RootShashinState& rootShashinState = shashinManager->getState();
11+
- constexpr bool PvNode = nodeType == PV;
12+
+ const bool isFortressNode = shashinManager->isFortress(pos);
13+
+ //by shashin definitions end
14+
+ constexpr bool PvNode = nodeType == PV;
15+
16+
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
17+
assert(PvNode || (alpha == beta - 1));
18+
@@ -2659,7 +2662,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
19+
// Accesso ottimizzato alle variabili di stato
20+
const bool isStrategical = dynamicDerived.isStrategical;
21+
const bool isAggressive = dynamicDerived.isAggressive;
22+
-
23+
+ // At non-PV nodes we check for an early TT cutoff
24+
if (!PvNode && is_valid(ttData.value)
25+
&& (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)))
26+
{
27+
@@ -2713,9 +2716,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
28+
updatedLearning = true;
29+
return expTTValue;
30+
}
31+
- // (Logica Learning per Quiet Kings omessa per brevità,
32+
- // mantenuta identica al blocco TT se necessario, o saltare allo step successivo)
33+
- // Qui mantengo il flusso originale per coerenza col tuo file
34+
+
35+
if (staticState.legalMoveCount < 35 && std::abs(expTTValue) > 60)
36+
{
37+
const bool areQuietKings =
38+
@@ -2749,12 +2750,14 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
39+
40+
if (ss->ttHit)
41+
{
42+
+ // Never assume anything about values stored in TT
43+
unadjustedStaticEval = ttData.eval;
44+
if (!is_valid(unadjustedStaticEval))
45+
unadjustedStaticEval = evaluate(pos);
46+
ss->staticEval = bestValue =
47+
to_corrected_static_eval(unadjustedStaticEval, correctionValue);
48+
49+
+ // ttValue can be used as a better position evaluation
50+
if (is_valid(ttData.value) && !is_decisive(ttData.value)
51+
&& (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER)))
52+
bestValue = ttData.value;
53+
@@ -2791,7 +2794,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
54+
futilityBase = ss->staticEval + 351;
55+
56+
// --- SHASHIN OPTIMIZATION 1: FUTILITY BASE (Outside Loop) ---
57+
- // Correggiamo il valore ridicolo (+3) con qualcosa di sensato.
58+
if (dynamicDerived.isHighTal)
59+
futilityBase += 30;
60+
else if (isStrategical)
61+
@@ -2802,6 +2804,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
62+
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory};
63+
Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;
64+
65+
+ // Initialize a MovePicker object for the current position, and prepare to search
66+
+ // the moves. We presently use two stages of move generator in quiescence search:
67+
+ // captures, or evasions only when in check.
68+
MovePicker mp(pos, ttData.move, DEPTH_QS, &mainHistory, &lowPlyHistory, &captureHistory,
69+
contHist, &sharedHistory, ss->ply);
70+
71+
@@ -2814,25 +2819,43 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
72+
moveCountThreshold = 1;
73+
if (staticState.kingDanger)
74+
moveCountThreshold++;
75+
- if (MoveConfig::isFortress)
76+
+ // Use isFortressNode (defined at top) instead of MoveConfig
77+
+ if (isFortressNode)
78+
moveCountThreshold++;
79+
80+
// B) SEE Margin
81+
- int seeMargin = -80; // Standard Stockfish
82+
+ // --- SHASHIN QSEARCH OPTIMIZATION ---
83+
+ int seeMargin = -80; // Default Stockfish
84+
if (isAggressive)
85+
- seeMargin -= 18; // Tal: -98
86+
+ {
87+
+ // Tal: Permette sacrifici speculativi se il Re è in pericolo
88+
+ seeMargin = staticState.kingDanger ? -150 : -105;
89+
+ }
90+
else if (isStrategical)
91+
- seeMargin += 18; // Petrosian: -62
92+
+ {
93+
+ // Petrosian: Richiede scambi solidi, non tollera perdite di materiale
94+
+ seeMargin = -35;
95+
+ }
96+
+ // Use isFortressNode (defined at top)
97+
+ else if (isFortressNode)
98+
+ {
99+
+ seeMargin = -10;
100+
+ }
101+
+ // ------------------------------------
102+
103+
// C) Volatility Check (Flag)
104+
const bool extremelyVolatile = dynamicDerived.isHighTal && staticState.kingDanger;
105+
// ----------------------------------------------------------------------
106+
107+
- // Step 5. Loop through all pseudo-legal moves
108+
+ // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta
109+
+ // cutoff occurs.
110+
while ((move = mp.next_move()) != Move::none())
111+
{
112+
assert(move.is_ok());
113+
114+
+ // FIX 2: Definiamo movedPiece qui perché serve per il controllo tattico Tal
115+
+ Piece movedPiece = pos.moved_piece(move);
116+
+
117+
if (!pos.legal(move))
118+
continue;
119+
120+
@@ -2841,61 +2864,60 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
121+
122+
moveCount++;
123+
124+
- // Step 6. Pruning (Optimized)
125+
+ // Step 6. Pruning
126+
if (!is_loss(bestValue))
127+
{
128+
// Futility pruning and moveCount pruning
129+
if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase)
130+
&& move.type_of() != PROMOTION)
131+
{
132+
- // Use pre-calculated threshold
133+
if (moveCount > moveCountThreshold)
134+
continue;
135+
136+
Value futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())];
137+
+
138+
+ // If static eval + value of piece we are going to capture is
139+
+ // much lower than alpha, we can prune this move.
140+
if (futilityValue <= alpha)
141+
{
142+
bestValue = std::max(bestValue, futilityValue);
143+
continue;
144+
}
145+
146+
- // === PATCH SHASHIN INTELLIGENTE (LOGICA INVERTITA) ===
147+
+ // If static exchange evaluation is low enough
148+
+ // we can prune this move.
149+
if (!pos.see_ge(move, alpha - futilityBase))
150+
{
151+
- // Logica tattica (vecchio comportamento) per:
152+
- // 1. Stili Tal aggressivi
153+
- // 2. Posizioni tattiche reattive
154+
- // 3. Pericolo del re (tranne in posizioni strategiche)
155+
- const bool useTacticalLogic = dynamicDerived.isHighTal
156+
- || dynamicDerived.isTacticalReactive
157+
- || (staticState.kingDanger && !isStrategical);
158+
-
159+
- if (useTacticalLogic)
160+
+ const bool riskyTactical =
161+
+ dynamicDerived.isHighTal || (staticState.kingDanger && !isStrategical);
162+
+
163+
+ if (riskyTactical)
164+
{
165+
- // COMPORTAMENTO TATTICO (ShashChess originale)
166+
- // Permette alla valutazione di scendere per esplorare mosse apparentemente negative
167+
- // ma potenzialmente vincenti (sacrifici, combinazioni nascoste)
168+
- bestValue = std::min(alpha, futilityBase);
169+
+ // Tal: Cerca comunque se non è un disastro totale (-200)
170+
+ if (!pos.see_ge(move, -200))
171+
+ {
172+
+ bestValue = std::max(bestValue, std::min(alpha, futilityBase));
173+
+ continue;
174+
+ }
175+
}
176+
else
177+
{
178+
- // COMPORTAMENTO STRATEGICO (patch Stockfish)
179+
- // Mantiene la valutazione stabile, evitando crolli ingiustificati
180+
- // Ottimo per posizioni strategiche e partite lunghe
181+
+ // Strategic/Standard: Fidati della valutazione statica
182+
bestValue = std::max(bestValue, std::min(alpha, futilityBase));
183+
+ continue;
184+
}
185+
- continue;
186+
}
187+
+ // ==============================
188+
}
189+
190+
// --- PHASE 4: HYBRID PRUNING OPTIMIZATION ---
191+
+ // Skip non-captures
192+
if (!capture)
193+
{
194+
if (extremelyVolatile) // Use pre-calculated flag
195+
{
196+
// Accesso costoso alla sharedHistory solo qui, nel caso critico
197+
- int historyScore =
198+
- sharedHistory.pawn_entry(pos)[pos.moved_piece(move)][move.to_sq()];
199+
+ int historyScore = sharedHistory.pawn_entry(pos)[movedPiece][move.to_sq()];
200+
201+
// Soglia fissa a 4000 (valore di sicurezza per mosse buone)
202+
if (historyScore < 4000)
203+
@@ -2903,16 +2925,27 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
204+
}
205+
else
206+
{
207+
- // COMPORTAMENTO STANDARD STOCKFISH:
208+
- // Se non è una cattura e non siamo in 'extreme volatility', potiamo tutto.
209+
+ // COMPORTAMENTO STANDARD STOCKFISH
210+
continue;
211+
}
212+
}
213+
// --- END PHASE 4 ---
214+
215+
- // SEE pruning (Use pre-calculated margin)
216+
- if (!pos.see_ge(move, seeMargin))
217+
+ // --- SHASHIN TACTICAL EXCEPTION ---
218+
+ bool forceCheckSearch = false;
219+
+ // Se siamo Tal/Aggressive e diamo scacco, ignoriamo il SEE negativo
220+
+ // (a meno che non sia un suicidio palese della Donna)
221+
+ if (givesCheck && (isAggressive || dynamicDerived.isHighTal))
222+
+ {
223+
+ if (type_of(movedPiece) != QUEEN || type_of(pos.piece_on(move.to_sq())) != PAWN)
224+
+ forceCheckSearch = true;
225+
+ }
226+
+
227+
+ // SEE pruning
228+
+ // Do not search moves with bad enough SEE values
229+
+ if (!forceCheckSearch && !pos.see_ge(move, seeMargin))
230+
continue;
231+
+ // ----------------------------------
232+
}
233+
234+

src/engine.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ Engine::Engine(std::optional<std::string> path) :
110110

111111
options.add("MultiPV", Option(1, 1, MAX_MOVES));
112112

113+
options.add("Anti Dancing Analysis", Option(false));
114+
113115
options.add("Move Overhead", Option(10, 0, 5000));
114116

115117
options.add("Minimum Thinking Time", Option(100, 0, 5000));

0 commit comments

Comments
 (0)