diff --git a/BanksiaIOS.xcodeproj/project.pbxproj b/BanksiaIOS.xcodeproj/project.pbxproj index 1411d92..f88ce63 100644 --- a/BanksiaIOS.xcodeproj/project.pbxproj +++ b/BanksiaIOS.xcodeproj/project.pbxproj @@ -56,6 +56,59 @@ B1026C4C2543268200603CC7 /* params.cc in Sources */ = {isa = PBXBuildFile; fileRef = B1026BF62543268200603CC7 /* params.cc */; }; B1026C4D2543268200603CC7 /* version.cc in Sources */ = {isa = PBXBuildFile; fileRef = B1026BF82543268200603CC7 /* version.cc */; }; B1026EC92543AB3100603CC7 /* BanksiaWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = B1C02B9D25294BEA00665CA6 /* BanksiaWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + B1044BD826A65E550032A27F /* thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044B9C26A65E550032A27F /* thread.cpp */; }; + B1044BD926A65E560032A27F /* timeman.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044B9E26A65E550032A27F /* timeman.cpp */; }; + B1044BDA26A65E560032A27F /* ucioption.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044B9F26A65E550032A27F /* ucioption.cpp */; }; + B1044BDB26A65E560032A27F /* evaluate_nnue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BA126A65E550032A27F /* evaluate_nnue.cpp */; }; + B1044BDC26A65E560032A27F /* half_ka_v2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BA926A65E550032A27F /* half_ka_v2.cpp */; }; + B1044BDD26A65E560032A27F /* misc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BAF26A65E550032A27F /* misc.cpp */; }; + B1044BDE26A65E560032A27F /* bitboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BB226A65E550032A27F /* bitboard.cpp */; }; + B1044BDF26A65E560032A27F /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = B1044BB326A65E550032A27F /* Makefile */; }; + B1044BE126A65E560032A27F /* psqt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BB626A65E550032A27F /* psqt.cpp */; }; + B1044BE226A65E560032A27F /* benchmark.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BB726A65E550032A27F /* benchmark.cpp */; }; + B1044BE326A65E560032A27F /* movepick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BB826A65E550032A27F /* movepick.cpp */; }; + B1044BE426A65E560032A27F /* tune.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BBA26A65E550032A27F /* tune.cpp */; }; + B1044BE526A65E560032A27F /* tbprobe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BBD26A65E550032A27F /* tbprobe.cpp */; }; + B1044BE626A65E560032A27F /* bitbase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BBF26A65E550032A27F /* bitbase.cpp */; }; + B1044BE726A65E560032A27F /* evaluate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BC026A65E550032A27F /* evaluate.cpp */; }; + B1044BE826A65E560032A27F /* search.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BC326A65E550032A27F /* search.cpp */; }; + B1044BE926A65E560032A27F /* pawns.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BC726A65E550032A27F /* pawns.cpp */; }; + B1044BEA26A65E560032A27F /* movegen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BC826A65E550032A27F /* movegen.cpp */; }; + B1044BEB26A65E560032A27F /* UNLICENCE in Resources */ = {isa = PBXBuildFile; fileRef = B1044BCA26A65E550032A27F /* UNLICENCE */; }; + B1044BEC26A65E560032A27F /* material.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BCC26A65E550032A27F /* material.cpp */; }; + B1044BED26A65E560032A27F /* .depend in Resources */ = {isa = PBXBuildFile; fileRef = B1044BCD26A65E550032A27F /* .depend */; }; + B1044BEE26A65E560032A27F /* tt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BCE26A65E550032A27F /* tt.cpp */; }; + B1044BEF26A65E560032A27F /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BD126A65E550032A27F /* main.cpp */; }; + B1044BF026A65E560032A27F /* position.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BD526A65E550032A27F /* position.cpp */; }; + B1044BF126A65E560032A27F /* uci.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BD626A65E550032A27F /* uci.cpp */; }; + B1044BF226A65E560032A27F /* endgame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044BD726A65E550032A27F /* endgame.cpp */; }; + B1044C8A26A666E70032A27F /* thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C4E26A666E60032A27F /* thread.cpp */; }; + B1044C8B26A666E70032A27F /* timeman.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C5026A666E60032A27F /* timeman.cpp */; }; + B1044C8C26A666E70032A27F /* ucioption.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C5126A666E60032A27F /* ucioption.cpp */; }; + B1044C8D26A666E70032A27F /* evaluate_nnue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C5326A666E60032A27F /* evaluate_nnue.cpp */; }; + B1044C8E26A666E70032A27F /* half_ka_v2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C5B26A666E60032A27F /* half_ka_v2.cpp */; }; + B1044C8F26A666E70032A27F /* misc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C6126A666E60032A27F /* misc.cpp */; }; + B1044C9026A666E70032A27F /* bitboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C6426A666E70032A27F /* bitboard.cpp */; }; + B1044C9326A666E70032A27F /* psqt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C6826A666E70032A27F /* psqt.cpp */; }; + B1044C9426A666E70032A27F /* benchmark.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C6926A666E70032A27F /* benchmark.cpp */; }; + B1044C9526A666E70032A27F /* movepick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C6A26A666E70032A27F /* movepick.cpp */; }; + B1044C9626A666E70032A27F /* tune.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C6C26A666E70032A27F /* tune.cpp */; }; + B1044C9726A666E70032A27F /* tbprobe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C6F26A666E70032A27F /* tbprobe.cpp */; }; + B1044C9826A666E70032A27F /* bitbase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C7126A666E70032A27F /* bitbase.cpp */; }; + B1044C9926A666E70032A27F /* evaluate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C7226A666E70032A27F /* evaluate.cpp */; }; + B1044C9A26A666E70032A27F /* search.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C7526A666E70032A27F /* search.cpp */; }; + B1044C9B26A666E70032A27F /* pawns.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C7926A666E70032A27F /* pawns.cpp */; }; + B1044C9C26A666E70032A27F /* movegen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C7A26A666E70032A27F /* movegen.cpp */; }; + B1044C9E26A666E70032A27F /* material.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C7E26A666E70032A27F /* material.cpp */; }; + B1044CA026A666E70032A27F /* tt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C8026A666E70032A27F /* tt.cpp */; }; + B1044CA126A666E70032A27F /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C8326A666E70032A27F /* main.cpp */; }; + B1044CA226A666E70032A27F /* position.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C8726A666E70032A27F /* position.cpp */; }; + B1044CA326A666E70032A27F /* uci.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C8826A666E70032A27F /* uci.cpp */; }; + B1044CA426A666E70032A27F /* endgame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1044C8926A666E70032A27F /* endgame.cpp */; }; + B1044CAA26A6684A0032A27F /* half_ka_v2.h in Sources */ = {isa = PBXBuildFile; fileRef = B1044BAA26A65E550032A27F /* half_ka_v2.h */; }; + B1044CB026A676A10032A27F /* nn-9e3c6298299a.nnue in Resources */ = {isa = PBXBuildFile; fileRef = B1044CAF26A676A10032A27F /* nn-9e3c6298299a.nnue */; }; + B1044CB126A676A10032A27F /* nn-9e3c6298299a.nnue in Resources */ = {isa = PBXBuildFile; fileRef = B1044CAF26A676A10032A27F /* nn-9e3c6298299a.nnue */; }; + B1044CB226A6773E0032A27F /* nn-9e3c6298299a.nnue in Resources */ = {isa = PBXBuildFile; fileRef = B1044CAF26A676A10032A27F /* nn-9e3c6298299a.nnue */; }; B12B8DBF25464C3600AD3096 /* convolution1.cc in Sources */ = {isa = PBXBuildFile; fileRef = B12B8DB325464C3600AD3096 /* convolution1.cc */; }; B12B8DC025464C3600AD3096 /* winograd_transform.ispc in Resources */ = {isa = PBXBuildFile; fileRef = B12B8DB425464C3600AD3096 /* winograd_transform.ispc */; }; B12B8DC125464C3600AD3096 /* winograd_convolution3.cc in Sources */ = {isa = PBXBuildFile; fileRef = B12B8DB625464C3600AD3096 /* winograd_convolution3.cc */; }; @@ -173,71 +226,6 @@ B14A8DE72528C8E000B5704C /* gen.c in Sources */ = {isa = PBXBuildFile; fileRef = B14A8CDE2528C8E000B5704C /* gen.c */; }; B14A8DE82528C8E000B5704C /* bitboard.c in Sources */ = {isa = PBXBuildFile; fileRef = B14A8CE12528C8E000B5704C /* bitboard.c */; }; B14A8DE92528C8E000B5704C /* engines.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8CE52528C8E000B5704C /* engines.cpp */; }; - B14A8E072528C8E000B5704C /* thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D1C2528C8E000B5704C /* thread.cpp */; }; - B14A8E082528C8E000B5704C /* timeman.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D1D2528C8E000B5704C /* timeman.cpp */; }; - B14A8E092528C8E000B5704C /* ucioption.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D1E2528C8E000B5704C /* ucioption.cpp */; }; - B14A8E0A2528C8E000B5704C /* evaluate_nnue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D202528C8E000B5704C /* evaluate_nnue.cpp */; }; - B14A8E0B2528C8E000B5704C /* half_kp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D2B2528C8E000B5704C /* half_kp.cpp */; }; - B14A8E0C2528C8E000B5704C /* misc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D332528C8E000B5704C /* misc.cpp */; }; - B14A8E0D2528C8E000B5704C /* bitboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D362528C8E000B5704C /* bitboard.cpp */; }; - B14A8E0E2528C8E000B5704C /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D372528C8E000B5704C /* Makefile */; }; - B14A8E0F2528C8E000B5704C /* psqt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D392528C8E000B5704C /* psqt.cpp */; }; - B14A8E102528C8E000B5704C /* benchmark.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D3A2528C8E000B5704C /* benchmark.cpp */; }; - B14A8E112528C8E000B5704C /* movepick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D3B2528C8E000B5704C /* movepick.cpp */; }; - B14A8E122528C8E000B5704C /* tune.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D3D2528C8E000B5704C /* tune.cpp */; }; - B14A8E132528C8E000B5704C /* tbprobe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D402528C8E000B5704C /* tbprobe.cpp */; }; - B14A8E142528C8E000B5704C /* bitbase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D422528C8E000B5704C /* bitbase.cpp */; }; - B14A8E152528C8E000B5704C /* evaluate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D432528C8E000B5704C /* evaluate.cpp */; }; - B14A8E162528C8E000B5704C /* search.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D462528C8E000B5704C /* search.cpp */; }; - B14A8E172528C8E000B5704C /* pawns.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D4A2528C8E000B5704C /* pawns.cpp */; }; - B14A8E182528C8E000B5704C /* movegen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D4B2528C8E000B5704C /* movegen.cpp */; }; - B14A8E1A2528C8E000B5704C /* material.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D4F2528C8E000B5704C /* material.cpp */; }; - B14A8E1B2528C8E000B5704C /* tt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D502528C8E000B5704C /* tt.cpp */; }; - B14A8E1C2528C8E000B5704C /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D532528C8E000B5704C /* main.cpp */; }; - B14A8E1D2528C8E000B5704C /* position.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D572528C8E000B5704C /* position.cpp */; }; - B14A8E1E2528C8E000B5704C /* uci.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D582528C8E000B5704C /* uci.cpp */; }; - B14A8E1F2528C8E000B5704C /* endgame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14A8D592528C8E000B5704C /* endgame.cpp */; }; - B14F4B222544D277004DE126 /* bitboards.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4AE52544D277004DE126 /* bitboards.cpp */; }; - B14F4B232544D277004DE126 /* utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4AE72544D277004DE126 /* utils.cpp */; }; - B14F4B242544D277004DE126 /* moveeval.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4AE92544D277004DE126 /* moveeval.cpp */; }; - B14F4B252544D277004DE126 /* igeltt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4AEA2544D277004DE126 /* igeltt.cpp */; }; - B14F4B262544D277004DE126 /* evaluate_nnue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4AED2544D277004DE126 /* evaluate_nnue.cpp */; }; - B14F4B272544D277004DE126 /* half_kp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4AF82544D277004DE126 /* half_kp.cpp */; }; - B14F4B282544D277004DE126 /* misc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4AFF2544D277004DE126 /* misc.cpp */; }; - B14F4B2A2544D277004DE126 /* makefile in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B022544D277004DE126 /* makefile */; }; - B14F4B2B2544D277004DE126 /* history.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B032544D277004DE126 /* history.cpp */; }; - B14F4B2C2544D277004DE126 /* texel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B052544D277004DE126 /* texel.cpp */; }; - B14F4B2D2544D277004DE126 /* evaluate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B062544D277004DE126 /* evaluate.cpp */; }; - B14F4B2F2544D277004DE126 /* tbprobe.c in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B0F2544D277004DE126 /* tbprobe.c */; }; - B14F4B312544D277004DE126 /* notation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B142544D277004DE126 /* notation.cpp */; }; - B14F4B322544D277004DE126 /* igelposition.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B162544D277004DE126 /* igelposition.cpp */; }; - B14F4B332544D277004DE126 /* eval_params.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B172544D277004DE126 /* eval_params.cpp */; }; - B14F4B342544D277004DE126 /* igelsearch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B192544D277004DE126 /* igelsearch.cpp */; }; - B14F4B362544D277004DE126 /* moves.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B1C2544D277004DE126 /* moves.cpp */; }; - B14F4B372544D277004DE126 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B1D2544D277004DE126 /* main.cpp */; }; - B14F4B382544D277004DE126 /* uci.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B1F2544D277004DE126 /* uci.cpp */; }; - B14F4B392544D277004DE126 /* time.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B202544D277004DE126 /* time.cpp */; }; - B14F4BCD2544D74A004DE126 /* bitboards.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B902544D749004DE126 /* bitboards.cpp */; }; - B14F4BCE2544D74A004DE126 /* utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B922544D749004DE126 /* utils.cpp */; }; - B14F4BCF2544D74A004DE126 /* moveeval.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B942544D749004DE126 /* moveeval.cpp */; }; - B14F4BD02544D74A004DE126 /* igeltt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B952544D74A004DE126 /* igeltt.cpp */; }; - B14F4BD12544D74A004DE126 /* evaluate_nnue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4B982544D74A004DE126 /* evaluate_nnue.cpp */; }; - B14F4BD22544D74A004DE126 /* half_kp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BA32544D74A004DE126 /* half_kp.cpp */; }; - B14F4BD32544D74A004DE126 /* misc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BAA2544D74A004DE126 /* misc.cpp */; }; - B14F4BD42544D74A004DE126 /* weights.txt in Resources */ = {isa = PBXBuildFile; fileRef = B14F4BAC2544D74A004DE126 /* weights.txt */; }; - B14F4BD52544D74A004DE126 /* makefile in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BAD2544D74A004DE126 /* makefile */; }; - B14F4BD62544D74A004DE126 /* history.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BAE2544D74A004DE126 /* history.cpp */; }; - B14F4BD72544D74A004DE126 /* texel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BB02544D74A004DE126 /* texel.cpp */; }; - B14F4BD82544D74A004DE126 /* evaluate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BB12544D74A004DE126 /* evaluate.cpp */; }; - B14F4BDA2544D74A004DE126 /* tbprobe.c in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BBA2544D74A004DE126 /* tbprobe.c */; }; - B14F4BDC2544D74A004DE126 /* notation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BBF2544D74A004DE126 /* notation.cpp */; }; - B14F4BDD2544D74A004DE126 /* igelposition.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BC12544D74A004DE126 /* igelposition.cpp */; }; - B14F4BDE2544D74A004DE126 /* eval_params.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BC22544D74A004DE126 /* eval_params.cpp */; }; - B14F4BDF2544D74A004DE126 /* igelsearch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BC42544D74A004DE126 /* igelsearch.cpp */; }; - B14F4BE12544D74A004DE126 /* moves.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BC72544D74A004DE126 /* moves.cpp */; }; - B14F4BE22544D74A004DE126 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BC82544D74A004DE126 /* main.cpp */; }; - B14F4BE32544D74A004DE126 /* uci.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BCA2544D74A004DE126 /* uci.cpp */; }; - B14F4BE42544D74A004DE126 /* time.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B14F4BCB2544D74A004DE126 /* time.cpp */; }; B17A8E51254976B500D5C329 /* Lc0Watch Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = B17A8E50254976B500D5C329 /* Lc0Watch Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; B17A8E61254976B600D5C329 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B17A8E60254976B600D5C329 /* Preview Assets.xcassets */; }; B17A8E65254976B600D5C329 /* Lc0Watch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = B17A8E44254976B400D5C329 /* Lc0Watch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -508,7 +496,6 @@ B1B602E0254423EC002B3E61 /* history.c in Sources */ = {isa = PBXBuildFile; fileRef = B1B600E2254423EA002B3E61 /* history.c */; }; B1B602E1254423EC002B3E61 /* bitboards.c in Sources */ = {isa = PBXBuildFile; fileRef = B1B600E4254423EA002B3E61 /* bitboards.c */; }; B1B602E2254423EC002B3E61 /* evalcache.c in Sources */ = {isa = PBXBuildFile; fileRef = B1B600E6254423EA002B3E61 /* evalcache.c */; }; - B1B602E3254423EC002B3E61 /* makefile in Sources */ = {isa = PBXBuildFile; fileRef = B1B600E9254423EA002B3E61 /* makefile */; }; B1B602E4254423EC002B3E61 /* move.c in Sources */ = {isa = PBXBuildFile; fileRef = B1B600EA254423EA002B3E61 /* move.c */; }; B1B602E5254423EC002B3E61 /* board.c in Sources */ = {isa = PBXBuildFile; fileRef = B1B600EB254423EA002B3E61 /* board.c */; }; B1B602E6254423EC002B3E61 /* pknet_224x32x1.net in Resources */ = {isa = PBXBuildFile; fileRef = B1B600ED254423EA002B3E61 /* pknet_224x32x1.net */; }; @@ -529,7 +516,6 @@ B1B602F7254423EC002B3E61 /* thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60115254423EA002B3E61 /* thread.cpp */; }; B1B602F8254423EC002B3E61 /* magic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60119254423EA002B3E61 /* magic.cpp */; }; B1B602F9254423EC002B3E61 /* bitboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6011C254423EA002B3E61 /* bitboard.cpp */; }; - B1B602FA254423EC002B3E61 /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = B1B6011D254423EA002B3E61 /* Makefile */; }; B1B602FB254423EC002B3E61 /* move_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6011E254423EA002B3E61 /* move_utils.cpp */; }; B1B602FC254423EC002B3E61 /* test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6011F254423EA002B3E61 /* test.cpp */; }; B1B602FD254423EC002B3E61 /* data.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60122254423EA002B3E61 /* data.cpp */; }; @@ -551,7 +537,6 @@ B1B60327254423ED002B3E61 /* stockfishlib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60186254423EA002B3E61 /* stockfishlib.cpp */; }; B1B60328254423ED002B3E61 /* common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60189254423EA002B3E61 /* common.cpp */; }; B1B60329254423ED002B3E61 /* moveorder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6018A254423EA002B3E61 /* moveorder.cpp */; }; - B1B6032A254423ED002B3E61 /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = B1B6018B254423EA002B3E61 /* Makefile */; }; B1B6032B254423ED002B3E61 /* bbinit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6018C254423EA002B3E61 /* bbinit.cpp */; }; B1B6032C254423ED002B3E61 /* board.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6018F254423EA002B3E61 /* board.cpp */; }; B1B6032D254423ED002B3E61 /* tbprobe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60191254423EA002B3E61 /* tbprobe.cpp */; }; @@ -580,7 +565,6 @@ B1B60347254423ED002B3E61 /* bitboard.c in Sources */ = {isa = PBXBuildFile; fileRef = B1B601CC254423EB002B3E61 /* bitboard.c */; }; B1B6034A254423ED002B3E61 /* engines.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B601EB254423EB002B3E61 /* engines.cpp */; }; B1B6035D254423ED002B3E61 /* utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60210254423EB002B3E61 /* utils.cpp */; }; - B1B6035E254423ED002B3E61 /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = B1B60211254423EB002B3E61 /* Makefile */; }; B1B6035F254423ED002B3E61 /* board.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60212254423EB002B3E61 /* board.cpp */; }; B1B60360254423ED002B3E61 /* tbprobe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60214254423EB002B3E61 /* tbprobe.cpp */; }; B1B60361254423ED002B3E61 /* nnue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60215254423EB002B3E61 /* nnue.cpp */; }; @@ -589,32 +573,7 @@ B1B60365254423ED002B3E61 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6021B254423EB002B3E61 /* main.cpp */; }; B1B60366254423ED002B3E61 /* eval.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6021C254423EB002B3E61 /* eval.cpp */; }; B1B60367254423ED002B3E61 /* uci.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6021D254423EB002B3E61 /* uci.cpp */; }; - B1B60368254423ED002B3E61 /* thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60222254423EB002B3E61 /* thread.cpp */; }; - B1B60369254423ED002B3E61 /* timeman.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60223254423EB002B3E61 /* timeman.cpp */; }; - B1B6036A254423ED002B3E61 /* ucioption.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60224254423EB002B3E61 /* ucioption.cpp */; }; - B1B6036B254423ED002B3E61 /* evaluate_nnue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60226254423EB002B3E61 /* evaluate_nnue.cpp */; }; - B1B6036C254423ED002B3E61 /* half_kp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60231254423EB002B3E61 /* half_kp.cpp */; }; - B1B6036D254423ED002B3E61 /* misc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60239254423EB002B3E61 /* misc.cpp */; }; - B1B6036E254423ED002B3E61 /* bitboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6023C254423EB002B3E61 /* bitboard.cpp */; }; - B1B6036F254423ED002B3E61 /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = B1B6023D254423EB002B3E61 /* Makefile */; }; - B1B60370254423ED002B3E61 /* psqt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6023F254423EB002B3E61 /* psqt.cpp */; }; - B1B60371254423ED002B3E61 /* benchmark.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60240254423EB002B3E61 /* benchmark.cpp */; }; - B1B60372254423ED002B3E61 /* movepick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60241254423EB002B3E61 /* movepick.cpp */; }; - B1B60373254423ED002B3E61 /* tune.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60243254423EB002B3E61 /* tune.cpp */; }; - B1B60374254423ED002B3E61 /* tbprobe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60246254423EB002B3E61 /* tbprobe.cpp */; }; - B1B60375254423ED002B3E61 /* bitbase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60248254423EB002B3E61 /* bitbase.cpp */; }; - B1B60376254423ED002B3E61 /* evaluate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60249254423EB002B3E61 /* evaluate.cpp */; }; - B1B60377254423ED002B3E61 /* search.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6024C254423EB002B3E61 /* search.cpp */; }; - B1B60378254423ED002B3E61 /* pawns.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60250254423EB002B3E61 /* pawns.cpp */; }; - B1B60379254423ED002B3E61 /* movegen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60251254423EB002B3E61 /* movegen.cpp */; }; - B1B6037B254423ED002B3E61 /* material.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60255254423EB002B3E61 /* material.cpp */; }; - B1B6037C254423ED002B3E61 /* tt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60256254423EB002B3E61 /* tt.cpp */; }; - B1B6037D254423ED002B3E61 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B60259254423EB002B3E61 /* main.cpp */; }; - B1B6037E254423ED002B3E61 /* position.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6025D254423EB002B3E61 /* position.cpp */; }; - B1B6037F254423ED002B3E61 /* uci.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6025E254423EB002B3E61 /* uci.cpp */; }; - B1B60380254423ED002B3E61 /* endgame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B1B6025F254423EB002B3E61 /* endgame.cpp */; }; B1B60381254423ED002B3E61 /* 703810.pb.gz in Resources */ = {isa = PBXBuildFile; fileRef = B1B60261254423EB002B3E61 /* 703810.pb.gz */; }; - B1B60382254423ED002B3E61 /* nn-03744f8d56d8.nnue in Resources */ = {isa = PBXBuildFile; fileRef = B1B60263254423EB002B3E61 /* nn-03744f8d56d8.nnue */; }; B1B605BC25443657002B3E61 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B19AFD0925430D370037B09E /* Accelerate.framework */; }; B1B605BD25443662002B3E61 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B19AFD41254311000037B09E /* libz.tbd */; }; B1B605CA25443678002B3E61 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B19AF68D2542E4210037B09E /* Accelerate.framework */; }; @@ -803,7 +762,6 @@ B1DA81CB255DE8770021C5DC /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DA81C3255DE8770021C5DC /* MenuView.swift */; }; B1DA81CC255DE8770021C5DC /* EditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DA81C4255DE8770021C5DC /* EditView.swift */; }; B1E0913B252BEB7100375B24 /* nn-03744f8d56d8.nnue in Resources */ = {isa = PBXBuildFile; fileRef = B1E0913A252BEB7100375B24 /* nn-03744f8d56d8.nnue */; }; - B1E09144252BEC4300375B24 /* nn-03744f8d56d8.nnue in Resources */ = {isa = PBXBuildFile; fileRef = B1E09143252BEC4300375B24 /* nn-03744f8d56d8.nnue */; }; B1E09156252BEC7D00375B24 /* nn-03744f8d56d8.nnue in Resources */ = {isa = PBXBuildFile; fileRef = B1E09155252BEC7D00375B24 /* nn-03744f8d56d8.nnue */; }; BA938BDACD0B5BEC6AF181B1 /* libPods-BanksiaIOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D1130534C70D97677100A723 /* libPods-BanksiaIOS.a */; }; C2F10B49F53EF158FDC241F2 /* libPods-Lc0.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B2168F7736A76B54EA33CDA5 /* libPods-Lc0.a */; }; @@ -1048,6 +1006,118 @@ B1026BF82543268200603CC7 /* version.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = version.cc; sourceTree = ""; }; B1026E8A2543AADD00603CC7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B1026E972543AADD00603CC7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B1044B9B26A65E550032A27F /* endgame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = endgame.h; sourceTree = ""; }; + B1044B9C26A65E550032A27F /* thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = thread.cpp; sourceTree = ""; }; + B1044B9D26A65E550032A27F /* psqt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = psqt.h; sourceTree = ""; }; + B1044B9E26A65E550032A27F /* timeman.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = timeman.cpp; sourceTree = ""; }; + B1044B9F26A65E550032A27F /* ucioption.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ucioption.cpp; sourceTree = ""; }; + B1044BA126A65E550032A27F /* evaluate_nnue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate_nnue.cpp; sourceTree = ""; }; + B1044BA326A65E550032A27F /* affine_transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = affine_transform.h; sourceTree = ""; }; + B1044BA426A65E550032A27F /* clipped_relu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clipped_relu.h; sourceTree = ""; }; + B1044BA526A65E550032A27F /* input_slice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = input_slice.h; sourceTree = ""; }; + B1044BA626A65E550032A27F /* nnue_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_common.h; sourceTree = ""; }; + B1044BA726A65E550032A27F /* nnue_feature_transformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_feature_transformer.h; sourceTree = ""; }; + B1044BA926A65E550032A27F /* half_ka_v2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = half_ka_v2.cpp; sourceTree = ""; }; + B1044BAA26A65E550032A27F /* half_ka_v2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = half_ka_v2.h; sourceTree = ""; }; + B1044BAB26A65E550032A27F /* evaluate_nnue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evaluate_nnue.h; sourceTree = ""; }; + B1044BAC26A65E550032A27F /* nnue_accumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_accumulator.h; sourceTree = ""; }; + B1044BAD26A65E550032A27F /* nnue_architecture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_architecture.h; sourceTree = ""; }; + B1044BAE26A65E550032A27F /* types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = types.h; sourceTree = ""; }; + B1044BAF26A65E550032A27F /* misc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = misc.cpp; sourceTree = ""; }; + B1044BB026A65E550032A27F /* tune.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tune.h; sourceTree = ""; }; + B1044BB126A65E550032A27F /* misc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = misc.h; sourceTree = ""; }; + B1044BB226A65E550032A27F /* bitboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitboard.cpp; sourceTree = ""; }; + B1044BB326A65E550032A27F /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + B1044BB526A65E550032A27F /* material.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = material.h; sourceTree = ""; }; + B1044BB626A65E550032A27F /* psqt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = psqt.cpp; sourceTree = ""; }; + B1044BB726A65E550032A27F /* benchmark.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = benchmark.cpp; sourceTree = ""; }; + B1044BB826A65E550032A27F /* movepick.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = movepick.cpp; sourceTree = ""; }; + B1044BB926A65E550032A27F /* bitboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitboard.h; sourceTree = ""; }; + B1044BBA26A65E550032A27F /* tune.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tune.cpp; sourceTree = ""; }; + B1044BBB26A65E550032A27F /* pawns.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pawns.h; sourceTree = ""; }; + B1044BBD26A65E550032A27F /* tbprobe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tbprobe.cpp; sourceTree = ""; }; + B1044BBE26A65E550032A27F /* tbprobe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbprobe.h; sourceTree = ""; }; + B1044BBF26A65E550032A27F /* bitbase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitbase.cpp; sourceTree = ""; }; + B1044BC026A65E550032A27F /* evaluate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate.cpp; sourceTree = ""; }; + B1044BC126A65E550032A27F /* thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread.h; sourceTree = ""; }; + B1044BC226A65E550032A27F /* movegen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = movegen.h; sourceTree = ""; }; + B1044BC326A65E550032A27F /* search.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = search.cpp; sourceTree = ""; }; + B1044BC426A65E550032A27F /* thread_win32_osx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread_win32_osx.h; sourceTree = ""; }; + B1044BC526A65E550032A27F /* movepick.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = movepick.h; sourceTree = ""; }; + B1044BC626A65E550032A27F /* evaluate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evaluate.h; sourceTree = ""; }; + B1044BC726A65E550032A27F /* pawns.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pawns.cpp; sourceTree = ""; }; + B1044BC826A65E550032A27F /* movegen.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = movegen.cpp; sourceTree = ""; }; + B1044BCA26A65E550032A27F /* UNLICENCE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = UNLICENCE; sourceTree = ""; }; + B1044BCB26A65E550032A27F /* incbin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = incbin.h; sourceTree = ""; }; + B1044BCC26A65E550032A27F /* material.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = material.cpp; sourceTree = ""; }; + B1044BCD26A65E550032A27F /* .depend */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .depend; sourceTree = ""; }; + B1044BCE26A65E550032A27F /* tt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tt.cpp; sourceTree = ""; }; + B1044BCF26A65E550032A27F /* search.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = search.h; sourceTree = ""; }; + B1044BD026A65E550032A27F /* tt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tt.h; sourceTree = ""; }; + B1044BD126A65E550032A27F /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + B1044BD226A65E550032A27F /* uci.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uci.h; sourceTree = ""; }; + B1044BD326A65E550032A27F /* position.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = position.h; sourceTree = ""; }; + B1044BD426A65E550032A27F /* timeman.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timeman.h; sourceTree = ""; }; + B1044BD526A65E550032A27F /* position.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = position.cpp; sourceTree = ""; }; + B1044BD626A65E550032A27F /* uci.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uci.cpp; sourceTree = ""; }; + B1044BD726A65E550032A27F /* endgame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = endgame.cpp; sourceTree = ""; }; + B1044C4D26A666E60032A27F /* endgame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = endgame.h; sourceTree = ""; }; + B1044C4E26A666E60032A27F /* thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = thread.cpp; sourceTree = ""; }; + B1044C4F26A666E60032A27F /* psqt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = psqt.h; sourceTree = ""; }; + B1044C5026A666E60032A27F /* timeman.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = timeman.cpp; sourceTree = ""; }; + B1044C5126A666E60032A27F /* ucioption.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ucioption.cpp; sourceTree = ""; }; + B1044C5326A666E60032A27F /* evaluate_nnue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate_nnue.cpp; sourceTree = ""; }; + B1044C5526A666E60032A27F /* affine_transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = affine_transform.h; sourceTree = ""; }; + B1044C5626A666E60032A27F /* clipped_relu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clipped_relu.h; sourceTree = ""; }; + B1044C5726A666E60032A27F /* input_slice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = input_slice.h; sourceTree = ""; }; + B1044C5826A666E60032A27F /* nnue_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_common.h; sourceTree = ""; }; + B1044C5926A666E60032A27F /* nnue_feature_transformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_feature_transformer.h; sourceTree = ""; }; + B1044C5B26A666E60032A27F /* half_ka_v2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = half_ka_v2.cpp; sourceTree = ""; }; + B1044C5C26A666E60032A27F /* half_ka_v2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = half_ka_v2.h; sourceTree = ""; }; + B1044C5D26A666E60032A27F /* evaluate_nnue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evaluate_nnue.h; sourceTree = ""; }; + B1044C5E26A666E60032A27F /* nnue_accumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_accumulator.h; sourceTree = ""; }; + B1044C5F26A666E60032A27F /* nnue_architecture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_architecture.h; sourceTree = ""; }; + B1044C6026A666E60032A27F /* types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = types.h; sourceTree = ""; }; + B1044C6126A666E60032A27F /* misc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = misc.cpp; sourceTree = ""; }; + B1044C6226A666E70032A27F /* tune.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tune.h; sourceTree = ""; }; + B1044C6326A666E70032A27F /* misc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = misc.h; sourceTree = ""; }; + B1044C6426A666E70032A27F /* bitboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitboard.cpp; sourceTree = ""; }; + B1044C6526A666E70032A27F /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + B1044C6626A666E70032A27F /* nn-9e3c6298299a.nnue */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nn-9e3c6298299a.nnue"; sourceTree = ""; }; + B1044C6726A666E70032A27F /* material.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = material.h; sourceTree = ""; }; + B1044C6826A666E70032A27F /* psqt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = psqt.cpp; sourceTree = ""; }; + B1044C6926A666E70032A27F /* benchmark.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = benchmark.cpp; sourceTree = ""; }; + B1044C6A26A666E70032A27F /* movepick.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = movepick.cpp; sourceTree = ""; }; + B1044C6B26A666E70032A27F /* bitboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitboard.h; sourceTree = ""; }; + B1044C6C26A666E70032A27F /* tune.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tune.cpp; sourceTree = ""; }; + B1044C6D26A666E70032A27F /* pawns.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pawns.h; sourceTree = ""; }; + B1044C6F26A666E70032A27F /* tbprobe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tbprobe.cpp; sourceTree = ""; }; + B1044C7026A666E70032A27F /* tbprobe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbprobe.h; sourceTree = ""; }; + B1044C7126A666E70032A27F /* bitbase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitbase.cpp; sourceTree = ""; }; + B1044C7226A666E70032A27F /* evaluate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate.cpp; sourceTree = ""; }; + B1044C7326A666E70032A27F /* thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread.h; sourceTree = ""; }; + B1044C7426A666E70032A27F /* movegen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = movegen.h; sourceTree = ""; }; + B1044C7526A666E70032A27F /* search.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = search.cpp; sourceTree = ""; }; + B1044C7626A666E70032A27F /* thread_win32_osx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread_win32_osx.h; sourceTree = ""; }; + B1044C7726A666E70032A27F /* movepick.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = movepick.h; sourceTree = ""; }; + B1044C7826A666E70032A27F /* evaluate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evaluate.h; sourceTree = ""; }; + B1044C7926A666E70032A27F /* pawns.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pawns.cpp; sourceTree = ""; }; + B1044C7A26A666E70032A27F /* movegen.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = movegen.cpp; sourceTree = ""; }; + B1044C7C26A666E70032A27F /* UNLICENCE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = UNLICENCE; sourceTree = ""; }; + B1044C7D26A666E70032A27F /* incbin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = incbin.h; sourceTree = ""; }; + B1044C7E26A666E70032A27F /* material.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = material.cpp; sourceTree = ""; }; + B1044C7F26A666E70032A27F /* .depend */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .depend; sourceTree = ""; }; + B1044C8026A666E70032A27F /* tt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tt.cpp; sourceTree = ""; }; + B1044C8126A666E70032A27F /* search.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = search.h; sourceTree = ""; }; + B1044C8226A666E70032A27F /* tt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tt.h; sourceTree = ""; }; + B1044C8326A666E70032A27F /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + B1044C8426A666E70032A27F /* uci.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uci.h; sourceTree = ""; }; + B1044C8526A666E70032A27F /* position.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = position.h; sourceTree = ""; }; + B1044C8626A666E70032A27F /* timeman.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timeman.h; sourceTree = ""; }; + B1044C8726A666E70032A27F /* position.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = position.cpp; sourceTree = ""; }; + B1044C8826A666E70032A27F /* uci.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uci.cpp; sourceTree = ""; }; + B1044C8926A666E70032A27F /* endgame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = endgame.cpp; sourceTree = ""; }; + B1044CAF26A676A10032A27F /* nn-9e3c6298299a.nnue */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nn-9e3c6298299a.nnue"; sourceTree = ""; }; B12B8DB325464C3600AD3096 /* convolution1.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = convolution1.cc; sourceTree = ""; }; B12B8DB425464C3600AD3096 /* winograd_transform.ispc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = winograd_transform.ispc; sourceTree = ""; }; B12B8DB525464C3600AD3096 /* winograd_convolution3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = winograd_convolution3.h; sourceTree = ""; }; @@ -1272,177 +1342,6 @@ B14A8CE52528C8E000B5704C /* engines.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = engines.cpp; sourceTree = ""; }; B14A8D182528C8E000B5704C /* engines-bridging-header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "engines-bridging-header.h"; sourceTree = ""; }; B14A8D192528C8E000B5704C /* engineids.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = engineids.h; sourceTree = ""; }; - B14A8D1B2528C8E000B5704C /* endgame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = endgame.h; sourceTree = ""; }; - B14A8D1C2528C8E000B5704C /* thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = thread.cpp; sourceTree = ""; }; - B14A8D1D2528C8E000B5704C /* timeman.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = timeman.cpp; sourceTree = ""; }; - B14A8D1E2528C8E000B5704C /* ucioption.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ucioption.cpp; sourceTree = ""; }; - B14A8D202528C8E000B5704C /* evaluate_nnue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate_nnue.cpp; sourceTree = ""; }; - B14A8D222528C8E000B5704C /* affine_transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = affine_transform.h; sourceTree = ""; }; - B14A8D232528C8E000B5704C /* clipped_relu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clipped_relu.h; sourceTree = ""; }; - B14A8D242528C8E000B5704C /* input_slice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = input_slice.h; sourceTree = ""; }; - B14A8D252528C8E000B5704C /* nnue_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_common.h; sourceTree = ""; }; - B14A8D262528C8E000B5704C /* nnue_feature_transformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_feature_transformer.h; sourceTree = ""; }; - B14A8D282528C8E000B5704C /* index_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = index_list.h; sourceTree = ""; }; - B14A8D292528C8E000B5704C /* features_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = features_common.h; sourceTree = ""; }; - B14A8D2A2528C8E000B5704C /* half_kp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = half_kp.h; sourceTree = ""; }; - B14A8D2B2528C8E000B5704C /* half_kp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = half_kp.cpp; sourceTree = ""; }; - B14A8D2C2528C8E000B5704C /* feature_set.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = feature_set.h; sourceTree = ""; }; - B14A8D2D2528C8E000B5704C /* evaluate_nnue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evaluate_nnue.h; sourceTree = ""; }; - B14A8D2F2528C8E000B5704C /* halfkp_256x2-32-32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "halfkp_256x2-32-32.h"; sourceTree = ""; }; - B14A8D302528C8E000B5704C /* nnue_accumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_accumulator.h; sourceTree = ""; }; - B14A8D312528C8E000B5704C /* nnue_architecture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_architecture.h; sourceTree = ""; }; - B14A8D322528C8E000B5704C /* types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = types.h; sourceTree = ""; }; - B14A8D332528C8E000B5704C /* misc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = misc.cpp; sourceTree = ""; }; - B14A8D342528C8E000B5704C /* tune.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tune.h; sourceTree = ""; }; - B14A8D352528C8E000B5704C /* misc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = misc.h; sourceTree = ""; }; - B14A8D362528C8E000B5704C /* bitboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitboard.cpp; sourceTree = ""; }; - B14A8D372528C8E000B5704C /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; - B14A8D382528C8E000B5704C /* material.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = material.h; sourceTree = ""; }; - B14A8D392528C8E000B5704C /* psqt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = psqt.cpp; sourceTree = ""; }; - B14A8D3A2528C8E000B5704C /* benchmark.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = benchmark.cpp; sourceTree = ""; }; - B14A8D3B2528C8E000B5704C /* movepick.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = movepick.cpp; sourceTree = ""; }; - B14A8D3C2528C8E000B5704C /* bitboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitboard.h; sourceTree = ""; }; - B14A8D3D2528C8E000B5704C /* tune.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tune.cpp; sourceTree = ""; }; - B14A8D3E2528C8E000B5704C /* pawns.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pawns.h; sourceTree = ""; }; - B14A8D402528C8E000B5704C /* tbprobe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tbprobe.cpp; sourceTree = ""; }; - B14A8D412528C8E000B5704C /* tbprobe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbprobe.h; sourceTree = ""; }; - B14A8D422528C8E000B5704C /* bitbase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitbase.cpp; sourceTree = ""; }; - B14A8D432528C8E000B5704C /* evaluate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate.cpp; sourceTree = ""; }; - B14A8D442528C8E000B5704C /* thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread.h; sourceTree = ""; }; - B14A8D452528C8E000B5704C /* movegen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = movegen.h; sourceTree = ""; }; - B14A8D462528C8E000B5704C /* search.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = search.cpp; sourceTree = ""; }; - B14A8D472528C8E000B5704C /* thread_win32_osx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread_win32_osx.h; sourceTree = ""; }; - B14A8D482528C8E000B5704C /* movepick.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = movepick.h; sourceTree = ""; }; - B14A8D492528C8E000B5704C /* evaluate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evaluate.h; sourceTree = ""; }; - B14A8D4A2528C8E000B5704C /* pawns.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pawns.cpp; sourceTree = ""; }; - B14A8D4B2528C8E000B5704C /* movegen.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = movegen.cpp; sourceTree = ""; }; - B14A8D4D2528C8E000B5704C /* UNLICENCE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = UNLICENCE; sourceTree = ""; }; - B14A8D4E2528C8E000B5704C /* incbin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = incbin.h; sourceTree = ""; }; - B14A8D4F2528C8E000B5704C /* material.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = material.cpp; sourceTree = ""; }; - B14A8D502528C8E000B5704C /* tt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tt.cpp; sourceTree = ""; }; - B14A8D512528C8E000B5704C /* search.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = search.h; sourceTree = ""; }; - B14A8D522528C8E000B5704C /* tt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tt.h; sourceTree = ""; }; - B14A8D532528C8E000B5704C /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; - B14A8D542528C8E000B5704C /* uci.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uci.h; sourceTree = ""; }; - B14A8D552528C8E000B5704C /* position.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = position.h; sourceTree = ""; }; - B14A8D562528C8E000B5704C /* timeman.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timeman.h; sourceTree = ""; }; - B14A8D572528C8E000B5704C /* position.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = position.cpp; sourceTree = ""; }; - B14A8D582528C8E000B5704C /* uci.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uci.cpp; sourceTree = ""; }; - B14A8D592528C8E000B5704C /* endgame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = endgame.cpp; sourceTree = ""; }; - B14F4AE42544D277004DE126 /* time.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = time.h; sourceTree = ""; }; - B14F4AE52544D277004DE126 /* bitboards.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitboards.cpp; sourceTree = ""; }; - B14F4AE62544D277004DE126 /* utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utils.h; sourceTree = ""; }; - B14F4AE72544D277004DE126 /* utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = utils.cpp; sourceTree = ""; }; - B14F4AE82544D277004DE126 /* igelsearch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igelsearch.h; sourceTree = ""; }; - B14F4AE92544D277004DE126 /* moveeval.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moveeval.cpp; sourceTree = ""; }; - B14F4AEA2544D277004DE126 /* igeltt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = igeltt.cpp; sourceTree = ""; }; - B14F4AEB2544D277004DE126 /* texel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = texel.h; sourceTree = ""; }; - B14F4AED2544D277004DE126 /* evaluate_nnue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate_nnue.cpp; sourceTree = ""; }; - B14F4AEF2544D277004DE126 /* affine_transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = affine_transform.h; sourceTree = ""; }; - B14F4AF02544D277004DE126 /* clipped_relu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clipped_relu.h; sourceTree = ""; }; - B14F4AF12544D277004DE126 /* input_slice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = input_slice.h; sourceTree = ""; }; - B14F4AF22544D277004DE126 /* nnue_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_common.h; sourceTree = ""; }; - B14F4AF32544D277004DE126 /* nnue_feature_transformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_feature_transformer.h; sourceTree = ""; }; - B14F4AF52544D277004DE126 /* index_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = index_list.h; sourceTree = ""; }; - B14F4AF62544D277004DE126 /* features_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = features_common.h; sourceTree = ""; }; - B14F4AF72544D277004DE126 /* half_kp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = half_kp.h; sourceTree = ""; }; - B14F4AF82544D277004DE126 /* half_kp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = half_kp.cpp; sourceTree = ""; }; - B14F4AF92544D277004DE126 /* feature_set.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = feature_set.h; sourceTree = ""; }; - B14F4AFA2544D277004DE126 /* evaluate_nnue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evaluate_nnue.h; sourceTree = ""; }; - B14F4AFC2544D277004DE126 /* halfkp_256x2-32-32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "halfkp_256x2-32-32.h"; sourceTree = ""; }; - B14F4AFD2544D277004DE126 /* nnue_accumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_accumulator.h; sourceTree = ""; }; - B14F4AFE2544D277004DE126 /* nnue_architecture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_architecture.h; sourceTree = ""; }; - B14F4AFF2544D277004DE126 /* misc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = misc.cpp; sourceTree = ""; }; - B14F4B002544D277004DE126 /* misc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = misc.h; sourceTree = ""; }; - B14F4B012544D277004DE126 /* weights.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = weights.txt; sourceTree = ""; }; - B14F4B022544D277004DE126 /* makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = makefile; sourceTree = ""; }; - B14F4B032544D277004DE126 /* history.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = history.cpp; sourceTree = ""; }; - B14F4B042544D277004DE126 /* eval_params.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = eval_params.h; sourceTree = ""; }; - B14F4B052544D277004DE126 /* texel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = texel.cpp; sourceTree = ""; }; - B14F4B062544D277004DE126 /* evaluate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate.cpp; sourceTree = ""; }; - B14F4B072544D277004DE126 /* igelposition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igelposition.h; sourceTree = ""; }; - B14F4B082544D277004DE126 /* history.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = history.h; sourceTree = ""; }; - B14F4B092544D277004DE126 /* igeltypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igeltypes.h; sourceTree = ""; }; - B14F4B0A2544D277004DE126 /* bitboards.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitboards.h; sourceTree = ""; }; - B14F4B0C2544D277004DE126 /* tbconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbconfig.h; sourceTree = ""; }; - B14F4B0D2544D277004DE126 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; - B14F4B0E2544D277004DE126 /* igeltbcore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igeltbcore.h; sourceTree = ""; }; - B14F4B0F2544D277004DE126 /* tbprobe.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tbprobe.c; sourceTree = ""; }; - B14F4B102544D277004DE126 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - B14F4B112544D277004DE126 /* tbcore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbcore.h; sourceTree = ""; }; - B14F4B122544D277004DE126 /* tbprobe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbprobe.h; sourceTree = ""; }; - B14F4B132544D277004DE126 /* igelevaluate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igelevaluate.h; sourceTree = ""; }; - B14F4B142544D277004DE126 /* notation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = notation.cpp; sourceTree = ""; }; - B14F4B152544D277004DE126 /* igeltt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igeltt.h; sourceTree = ""; }; - B14F4B162544D277004DE126 /* igelposition.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = igelposition.cpp; sourceTree = ""; }; - B14F4B172544D277004DE126 /* eval_params.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = eval_params.cpp; sourceTree = ""; }; - B14F4B182544D277004DE126 /* notation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = notation.h; sourceTree = ""; }; - B14F4B192544D277004DE126 /* igelsearch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = igelsearch.cpp; sourceTree = ""; }; - B14F4B1A2544D277004DE126 /* bench.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = bench.csv; sourceTree = ""; }; - B14F4B1B2544D277004DE126 /* moveeval.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = moveeval.h; sourceTree = ""; }; - B14F4B1C2544D277004DE126 /* moves.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moves.cpp; sourceTree = ""; }; - B14F4B1D2544D277004DE126 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; - B14F4B1E2544D277004DE126 /* igeluci.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igeluci.h; sourceTree = ""; }; - B14F4B1F2544D277004DE126 /* uci.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uci.cpp; sourceTree = ""; }; - B14F4B202544D277004DE126 /* time.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = time.cpp; sourceTree = ""; }; - B14F4B212544D277004DE126 /* moves.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = moves.h; sourceTree = ""; }; - B14F4B8F2544D749004DE126 /* time.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = time.h; sourceTree = ""; }; - B14F4B902544D749004DE126 /* bitboards.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitboards.cpp; sourceTree = ""; }; - B14F4B912544D749004DE126 /* utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utils.h; sourceTree = ""; }; - B14F4B922544D749004DE126 /* utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = utils.cpp; sourceTree = ""; }; - B14F4B932544D749004DE126 /* igelsearch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igelsearch.h; sourceTree = ""; }; - B14F4B942544D749004DE126 /* moveeval.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moveeval.cpp; sourceTree = ""; }; - B14F4B952544D74A004DE126 /* igeltt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = igeltt.cpp; sourceTree = ""; }; - B14F4B962544D74A004DE126 /* texel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = texel.h; sourceTree = ""; }; - B14F4B982544D74A004DE126 /* evaluate_nnue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate_nnue.cpp; sourceTree = ""; }; - B14F4B9A2544D74A004DE126 /* affine_transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = affine_transform.h; sourceTree = ""; }; - B14F4B9B2544D74A004DE126 /* clipped_relu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clipped_relu.h; sourceTree = ""; }; - B14F4B9C2544D74A004DE126 /* input_slice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = input_slice.h; sourceTree = ""; }; - B14F4B9D2544D74A004DE126 /* nnue_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_common.h; sourceTree = ""; }; - B14F4B9E2544D74A004DE126 /* nnue_feature_transformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_feature_transformer.h; sourceTree = ""; }; - B14F4BA02544D74A004DE126 /* index_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = index_list.h; sourceTree = ""; }; - B14F4BA12544D74A004DE126 /* features_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = features_common.h; sourceTree = ""; }; - B14F4BA22544D74A004DE126 /* half_kp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = half_kp.h; sourceTree = ""; }; - B14F4BA32544D74A004DE126 /* half_kp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = half_kp.cpp; sourceTree = ""; }; - B14F4BA42544D74A004DE126 /* feature_set.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = feature_set.h; sourceTree = ""; }; - B14F4BA52544D74A004DE126 /* evaluate_nnue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evaluate_nnue.h; sourceTree = ""; }; - B14F4BA72544D74A004DE126 /* halfkp_256x2-32-32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "halfkp_256x2-32-32.h"; sourceTree = ""; }; - B14F4BA82544D74A004DE126 /* nnue_accumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_accumulator.h; sourceTree = ""; }; - B14F4BA92544D74A004DE126 /* nnue_architecture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_architecture.h; sourceTree = ""; }; - B14F4BAA2544D74A004DE126 /* misc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = misc.cpp; sourceTree = ""; }; - B14F4BAB2544D74A004DE126 /* misc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = misc.h; sourceTree = ""; }; - B14F4BAC2544D74A004DE126 /* weights.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = weights.txt; sourceTree = ""; }; - B14F4BAD2544D74A004DE126 /* makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = makefile; sourceTree = ""; }; - B14F4BAE2544D74A004DE126 /* history.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = history.cpp; sourceTree = ""; }; - B14F4BAF2544D74A004DE126 /* eval_params.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = eval_params.h; sourceTree = ""; }; - B14F4BB02544D74A004DE126 /* texel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = texel.cpp; sourceTree = ""; }; - B14F4BB12544D74A004DE126 /* evaluate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate.cpp; sourceTree = ""; }; - B14F4BB22544D74A004DE126 /* igelposition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igelposition.h; sourceTree = ""; }; - B14F4BB32544D74A004DE126 /* history.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = history.h; sourceTree = ""; }; - B14F4BB42544D74A004DE126 /* igeltypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igeltypes.h; sourceTree = ""; }; - B14F4BB52544D74A004DE126 /* bitboards.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitboards.h; sourceTree = ""; }; - B14F4BB72544D74A004DE126 /* tbconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbconfig.h; sourceTree = ""; }; - B14F4BB82544D74A004DE126 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; - B14F4BB92544D74A004DE126 /* igeltbcore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igeltbcore.h; sourceTree = ""; }; - B14F4BBA2544D74A004DE126 /* tbprobe.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tbprobe.c; sourceTree = ""; }; - B14F4BBB2544D74A004DE126 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - B14F4BBC2544D74A004DE126 /* tbcore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbcore.h; sourceTree = ""; }; - B14F4BBD2544D74A004DE126 /* tbprobe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbprobe.h; sourceTree = ""; }; - B14F4BBE2544D74A004DE126 /* igelevaluate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igelevaluate.h; sourceTree = ""; }; - B14F4BBF2544D74A004DE126 /* notation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = notation.cpp; sourceTree = ""; }; - B14F4BC02544D74A004DE126 /* igeltt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igeltt.h; sourceTree = ""; }; - B14F4BC12544D74A004DE126 /* igelposition.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = igelposition.cpp; sourceTree = ""; }; - B14F4BC22544D74A004DE126 /* eval_params.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = eval_params.cpp; sourceTree = ""; }; - B14F4BC32544D74A004DE126 /* notation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = notation.h; sourceTree = ""; }; - B14F4BC42544D74A004DE126 /* igelsearch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = igelsearch.cpp; sourceTree = ""; }; - B14F4BC52544D74A004DE126 /* bench.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = bench.csv; sourceTree = ""; }; - B14F4BC62544D74A004DE126 /* moveeval.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = moveeval.h; sourceTree = ""; }; - B14F4BC72544D74A004DE126 /* moves.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moves.cpp; sourceTree = ""; }; - B14F4BC82544D74A004DE126 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; - B14F4BC92544D74A004DE126 /* igeluci.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igeluci.h; sourceTree = ""; }; - B14F4BCA2544D74A004DE126 /* uci.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uci.cpp; sourceTree = ""; }; - B14F4BCB2544D74A004DE126 /* time.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = time.cpp; sourceTree = ""; }; - B14F4BCC2544D74A004DE126 /* moves.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = moves.h; sourceTree = ""; }; B17A8E44254976B400D5C329 /* Lc0Watch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Lc0Watch.app; sourceTree = BUILT_PRODUCTS_DIR; }; B17A8E4B254976B500D5C329 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B17A8E50254976B500D5C329 /* Lc0Watch Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Lc0Watch Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2042,63 +1941,6 @@ B1B6021D254423EB002B3E61 /* uci.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uci.cpp; sourceTree = ""; }; B1B6021E254423EB002B3E61 /* engines-bridging-header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "engines-bridging-header.h"; sourceTree = ""; }; B1B6021F254423EB002B3E61 /* engineids.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = engineids.h; sourceTree = ""; }; - B1B60221254423EB002B3E61 /* endgame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = endgame.h; sourceTree = ""; }; - B1B60222254423EB002B3E61 /* thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = thread.cpp; sourceTree = ""; }; - B1B60223254423EB002B3E61 /* timeman.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = timeman.cpp; sourceTree = ""; }; - B1B60224254423EB002B3E61 /* ucioption.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ucioption.cpp; sourceTree = ""; }; - B1B60226254423EB002B3E61 /* evaluate_nnue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate_nnue.cpp; sourceTree = ""; }; - B1B60228254423EB002B3E61 /* affine_transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = affine_transform.h; sourceTree = ""; }; - B1B60229254423EB002B3E61 /* clipped_relu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clipped_relu.h; sourceTree = ""; }; - B1B6022A254423EB002B3E61 /* input_slice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = input_slice.h; sourceTree = ""; }; - B1B6022B254423EB002B3E61 /* nnue_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_common.h; sourceTree = ""; }; - B1B6022C254423EB002B3E61 /* nnue_feature_transformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_feature_transformer.h; sourceTree = ""; }; - B1B6022E254423EB002B3E61 /* index_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = index_list.h; sourceTree = ""; }; - B1B6022F254423EB002B3E61 /* features_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = features_common.h; sourceTree = ""; }; - B1B60230254423EB002B3E61 /* half_kp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = half_kp.h; sourceTree = ""; }; - B1B60231254423EB002B3E61 /* half_kp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = half_kp.cpp; sourceTree = ""; }; - B1B60232254423EB002B3E61 /* feature_set.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = feature_set.h; sourceTree = ""; }; - B1B60233254423EB002B3E61 /* evaluate_nnue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evaluate_nnue.h; sourceTree = ""; }; - B1B60235254423EB002B3E61 /* halfkp_256x2-32-32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "halfkp_256x2-32-32.h"; sourceTree = ""; }; - B1B60236254423EB002B3E61 /* nnue_accumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_accumulator.h; sourceTree = ""; }; - B1B60237254423EB002B3E61 /* nnue_architecture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nnue_architecture.h; sourceTree = ""; }; - B1B60238254423EB002B3E61 /* types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = types.h; sourceTree = ""; }; - B1B60239254423EB002B3E61 /* misc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = misc.cpp; sourceTree = ""; }; - B1B6023A254423EB002B3E61 /* tune.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tune.h; sourceTree = ""; }; - B1B6023B254423EB002B3E61 /* misc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = misc.h; sourceTree = ""; }; - B1B6023C254423EB002B3E61 /* bitboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitboard.cpp; sourceTree = ""; }; - B1B6023D254423EB002B3E61 /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; - B1B6023E254423EB002B3E61 /* material.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = material.h; sourceTree = ""; }; - B1B6023F254423EB002B3E61 /* psqt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = psqt.cpp; sourceTree = ""; }; - B1B60240254423EB002B3E61 /* benchmark.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = benchmark.cpp; sourceTree = ""; }; - B1B60241254423EB002B3E61 /* movepick.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = movepick.cpp; sourceTree = ""; }; - B1B60242254423EB002B3E61 /* bitboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitboard.h; sourceTree = ""; }; - B1B60243254423EB002B3E61 /* tune.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tune.cpp; sourceTree = ""; }; - B1B60244254423EB002B3E61 /* pawns.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pawns.h; sourceTree = ""; }; - B1B60246254423EB002B3E61 /* tbprobe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tbprobe.cpp; sourceTree = ""; }; - B1B60247254423EB002B3E61 /* tbprobe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbprobe.h; sourceTree = ""; }; - B1B60248254423EB002B3E61 /* bitbase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bitbase.cpp; sourceTree = ""; }; - B1B60249254423EB002B3E61 /* evaluate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = evaluate.cpp; sourceTree = ""; }; - B1B6024A254423EB002B3E61 /* thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread.h; sourceTree = ""; }; - B1B6024B254423EB002B3E61 /* movegen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = movegen.h; sourceTree = ""; }; - B1B6024C254423EB002B3E61 /* search.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = search.cpp; sourceTree = ""; }; - B1B6024D254423EB002B3E61 /* thread_win32_osx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread_win32_osx.h; sourceTree = ""; }; - B1B6024E254423EB002B3E61 /* movepick.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = movepick.h; sourceTree = ""; }; - B1B6024F254423EB002B3E61 /* evaluate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = evaluate.h; sourceTree = ""; }; - B1B60250254423EB002B3E61 /* pawns.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pawns.cpp; sourceTree = ""; }; - B1B60251254423EB002B3E61 /* movegen.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = movegen.cpp; sourceTree = ""; }; - B1B60253254423EB002B3E61 /* UNLICENCE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = UNLICENCE; sourceTree = ""; }; - B1B60254254423EB002B3E61 /* incbin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = incbin.h; sourceTree = ""; }; - B1B60255254423EB002B3E61 /* material.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = material.cpp; sourceTree = ""; }; - B1B60256254423EB002B3E61 /* tt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tt.cpp; sourceTree = ""; }; - B1B60257254423EB002B3E61 /* search.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = search.h; sourceTree = ""; }; - B1B60258254423EB002B3E61 /* tt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tt.h; sourceTree = ""; }; - B1B60259254423EB002B3E61 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; - B1B6025A254423EB002B3E61 /* uci.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uci.h; sourceTree = ""; }; - B1B6025B254423EB002B3E61 /* position.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = position.h; sourceTree = ""; }; - B1B6025C254423EB002B3E61 /* timeman.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timeman.h; sourceTree = ""; }; - B1B6025D254423EB002B3E61 /* position.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = position.cpp; sourceTree = ""; }; - B1B6025E254423EB002B3E61 /* uci.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uci.cpp; sourceTree = ""; }; - B1B6025F254423EB002B3E61 /* endgame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = endgame.cpp; sourceTree = ""; }; B1B60261254423EB002B3E61 /* 703810.pb.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = 703810.pb.gz; sourceTree = ""; }; B1B60263254423EB002B3E61 /* nn-03744f8d56d8.nnue */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nn-03744f8d56d8.nnue"; sourceTree = ""; }; B1B6060725446320002B3E61 /* 703810.pb.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = 703810.pb.gz; sourceTree = ""; }; @@ -2402,7 +2244,6 @@ B1DA81C3255DE8770021C5DC /* MenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; }; B1DA81C4255DE8770021C5DC /* EditView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditView.swift; sourceTree = ""; }; B1E0913A252BEB7100375B24 /* nn-03744f8d56d8.nnue */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nn-03744f8d56d8.nnue"; sourceTree = ""; }; - B1E09143252BEC4300375B24 /* nn-03744f8d56d8.nnue */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nn-03744f8d56d8.nnue"; sourceTree = ""; }; B1E09155252BEC7D00375B24 /* nn-03744f8d56d8.nnue */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nn-03744f8d56d8.nnue"; sourceTree = ""; }; B2168F7736A76B54EA33CDA5 /* libPods-Lc0.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Lc0.a"; sourceTree = BUILT_PRODUCTS_DIR; }; B5FDFEF4D637749094EE09F7 /* libPods-NN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2739,6 +2580,219 @@ path = "BanksiaWatch Extension"; sourceTree = ""; }; + B1044B9A26A65E550032A27F /* stockfish */ = { + isa = PBXGroup; + children = ( + B1044B9B26A65E550032A27F /* endgame.h */, + B1044B9C26A65E550032A27F /* thread.cpp */, + B1044B9D26A65E550032A27F /* psqt.h */, + B1044B9E26A65E550032A27F /* timeman.cpp */, + B1044B9F26A65E550032A27F /* ucioption.cpp */, + B1044BA026A65E550032A27F /* nnue */, + B1044BAE26A65E550032A27F /* types.h */, + B1044BAF26A65E550032A27F /* misc.cpp */, + B1044BB026A65E550032A27F /* tune.h */, + B1044BB126A65E550032A27F /* misc.h */, + B1044BB226A65E550032A27F /* bitboard.cpp */, + B1044BB326A65E550032A27F /* Makefile */, + B1044BB526A65E550032A27F /* material.h */, + B1044BB626A65E550032A27F /* psqt.cpp */, + B1044BB726A65E550032A27F /* benchmark.cpp */, + B1044BB826A65E550032A27F /* movepick.cpp */, + B1044BB926A65E550032A27F /* bitboard.h */, + B1044BBA26A65E550032A27F /* tune.cpp */, + B1044BBB26A65E550032A27F /* pawns.h */, + B1044BBC26A65E550032A27F /* syzygy */, + B1044BBF26A65E550032A27F /* bitbase.cpp */, + B1044BC026A65E550032A27F /* evaluate.cpp */, + B1044BC126A65E550032A27F /* thread.h */, + B1044BC226A65E550032A27F /* movegen.h */, + B1044BC326A65E550032A27F /* search.cpp */, + B1044BC426A65E550032A27F /* thread_win32_osx.h */, + B1044BC526A65E550032A27F /* movepick.h */, + B1044BC626A65E550032A27F /* evaluate.h */, + B1044BC726A65E550032A27F /* pawns.cpp */, + B1044BC826A65E550032A27F /* movegen.cpp */, + B1044BC926A65E550032A27F /* incbin */, + B1044BCC26A65E550032A27F /* material.cpp */, + B1044BCD26A65E550032A27F /* .depend */, + B1044BCE26A65E550032A27F /* tt.cpp */, + B1044BCF26A65E550032A27F /* search.h */, + B1044BD026A65E550032A27F /* tt.h */, + B1044BD126A65E550032A27F /* main.cpp */, + B1044BD226A65E550032A27F /* uci.h */, + B1044BD326A65E550032A27F /* position.h */, + B1044BD426A65E550032A27F /* timeman.h */, + B1044BD526A65E550032A27F /* position.cpp */, + B1044BD626A65E550032A27F /* uci.cpp */, + B1044BD726A65E550032A27F /* endgame.cpp */, + ); + path = stockfish; + sourceTree = ""; + }; + B1044BA026A65E550032A27F /* nnue */ = { + isa = PBXGroup; + children = ( + B1044BA126A65E550032A27F /* evaluate_nnue.cpp */, + B1044BA226A65E550032A27F /* layers */, + B1044BA626A65E550032A27F /* nnue_common.h */, + B1044BA726A65E550032A27F /* nnue_feature_transformer.h */, + B1044BA826A65E550032A27F /* features */, + B1044BAB26A65E550032A27F /* evaluate_nnue.h */, + B1044BAC26A65E550032A27F /* nnue_accumulator.h */, + B1044BAD26A65E550032A27F /* nnue_architecture.h */, + ); + path = nnue; + sourceTree = ""; + }; + B1044BA226A65E550032A27F /* layers */ = { + isa = PBXGroup; + children = ( + B1044BA326A65E550032A27F /* affine_transform.h */, + B1044BA426A65E550032A27F /* clipped_relu.h */, + B1044BA526A65E550032A27F /* input_slice.h */, + ); + path = layers; + sourceTree = ""; + }; + B1044BA826A65E550032A27F /* features */ = { + isa = PBXGroup; + children = ( + B1044BA926A65E550032A27F /* half_ka_v2.cpp */, + B1044BAA26A65E550032A27F /* half_ka_v2.h */, + ); + path = features; + sourceTree = ""; + }; + B1044BBC26A65E550032A27F /* syzygy */ = { + isa = PBXGroup; + children = ( + B1044BBD26A65E550032A27F /* tbprobe.cpp */, + B1044BBE26A65E550032A27F /* tbprobe.h */, + ); + path = syzygy; + sourceTree = ""; + }; + B1044BC926A65E550032A27F /* incbin */ = { + isa = PBXGroup; + children = ( + B1044BCA26A65E550032A27F /* UNLICENCE */, + B1044BCB26A65E550032A27F /* incbin.h */, + ); + path = incbin; + sourceTree = ""; + }; + B1044C4C26A666E60032A27F /* stockfish */ = { + isa = PBXGroup; + children = ( + B1044C4D26A666E60032A27F /* endgame.h */, + B1044C4E26A666E60032A27F /* thread.cpp */, + B1044C4F26A666E60032A27F /* psqt.h */, + B1044C5026A666E60032A27F /* timeman.cpp */, + B1044C5126A666E60032A27F /* ucioption.cpp */, + B1044C5226A666E60032A27F /* nnue */, + B1044C6026A666E60032A27F /* types.h */, + B1044C6126A666E60032A27F /* misc.cpp */, + B1044C6226A666E70032A27F /* tune.h */, + B1044C6326A666E70032A27F /* misc.h */, + B1044C6426A666E70032A27F /* bitboard.cpp */, + B1044C6526A666E70032A27F /* Makefile */, + B1044C6626A666E70032A27F /* nn-9e3c6298299a.nnue */, + B1044C6726A666E70032A27F /* material.h */, + B1044C6826A666E70032A27F /* psqt.cpp */, + B1044C6926A666E70032A27F /* benchmark.cpp */, + B1044C6A26A666E70032A27F /* movepick.cpp */, + B1044C6B26A666E70032A27F /* bitboard.h */, + B1044C6C26A666E70032A27F /* tune.cpp */, + B1044C6D26A666E70032A27F /* pawns.h */, + B1044C6E26A666E70032A27F /* syzygy */, + B1044C7126A666E70032A27F /* bitbase.cpp */, + B1044C7226A666E70032A27F /* evaluate.cpp */, + B1044C7326A666E70032A27F /* thread.h */, + B1044C7426A666E70032A27F /* movegen.h */, + B1044C7526A666E70032A27F /* search.cpp */, + B1044C7626A666E70032A27F /* thread_win32_osx.h */, + B1044C7726A666E70032A27F /* movepick.h */, + B1044C7826A666E70032A27F /* evaluate.h */, + B1044C7926A666E70032A27F /* pawns.cpp */, + B1044C7A26A666E70032A27F /* movegen.cpp */, + B1044C7B26A666E70032A27F /* incbin */, + B1044C7E26A666E70032A27F /* material.cpp */, + B1044C7F26A666E70032A27F /* .depend */, + B1044C8026A666E70032A27F /* tt.cpp */, + B1044C8126A666E70032A27F /* search.h */, + B1044C8226A666E70032A27F /* tt.h */, + B1044C8326A666E70032A27F /* main.cpp */, + B1044C8426A666E70032A27F /* uci.h */, + B1044C8526A666E70032A27F /* position.h */, + B1044C8626A666E70032A27F /* timeman.h */, + B1044C8726A666E70032A27F /* position.cpp */, + B1044C8826A666E70032A27F /* uci.cpp */, + B1044C8926A666E70032A27F /* endgame.cpp */, + ); + path = stockfish; + sourceTree = ""; + }; + B1044C5226A666E60032A27F /* nnue */ = { + isa = PBXGroup; + children = ( + B1044C5326A666E60032A27F /* evaluate_nnue.cpp */, + B1044C5426A666E60032A27F /* layers */, + B1044C5826A666E60032A27F /* nnue_common.h */, + B1044C5926A666E60032A27F /* nnue_feature_transformer.h */, + B1044C5A26A666E60032A27F /* features */, + B1044C5D26A666E60032A27F /* evaluate_nnue.h */, + B1044C5E26A666E60032A27F /* nnue_accumulator.h */, + B1044C5F26A666E60032A27F /* nnue_architecture.h */, + ); + path = nnue; + sourceTree = ""; + }; + B1044C5426A666E60032A27F /* layers */ = { + isa = PBXGroup; + children = ( + B1044C5526A666E60032A27F /* affine_transform.h */, + B1044C5626A666E60032A27F /* clipped_relu.h */, + B1044C5726A666E60032A27F /* input_slice.h */, + ); + path = layers; + sourceTree = ""; + }; + B1044C5A26A666E60032A27F /* features */ = { + isa = PBXGroup; + children = ( + B1044C5B26A666E60032A27F /* half_ka_v2.cpp */, + B1044C5C26A666E60032A27F /* half_ka_v2.h */, + ); + path = features; + sourceTree = ""; + }; + B1044C6E26A666E70032A27F /* syzygy */ = { + isa = PBXGroup; + children = ( + B1044C6F26A666E70032A27F /* tbprobe.cpp */, + B1044C7026A666E70032A27F /* tbprobe.h */, + ); + path = syzygy; + sourceTree = ""; + }; + B1044C7B26A666E70032A27F /* incbin */ = { + isa = PBXGroup; + children = ( + B1044C7C26A666E70032A27F /* UNLICENCE */, + B1044C7D26A666E70032A27F /* incbin.h */, + ); + path = incbin; + sourceTree = ""; + }; + B1044CAE26A676A10032A27F /* nnuenets */ = { + isa = PBXGroup; + children = ( + B1044CAF26A676A10032A27F /* nn-9e3c6298299a.nnue */, + ); + path = nnuenets; + sourceTree = SOURCE_ROOT; + }; B12B8DB225464C3600AD3096 /* blas */ = { isa = PBXGroup; children = ( @@ -2797,12 +2851,12 @@ B14A8A9B2528C48900B5704C /* BanksiaIOS */ = { isa = PBXGroup; children = ( + B1044CAE26A676A10032A27F /* nnuenets */, B14A8B3C2528C76500B5704C /* views */, B1DA8193255DE8520021C5DC /* views-ios */, B1B6060625446320002B3E61 /* lc0net */, B1A5A6C12532ED6C0007A258 /* options-ios */, B14A8B5D2528C76500B5704C /* graphics */, - B1E09142252BEC4300375B24 /* nnuenets */, B1C02B062528D43600665CA6 /* Base.lproj */, B14A8BAB2528C8DF00B5704C /* engines */, B14A8B252528C76500B5704C /* 3rdlib */, @@ -2933,7 +2987,7 @@ B14A8BAB2528C8DF00B5704C /* engines */ = { isa = PBXGroup; children = ( - B14F4AE32544D277004DE126 /* igel */, + B1044B9A26A65E550032A27F /* stockfish */, B1026A232543268200603CC7 /* lc0 */, B19AE96C2540D6460037B09E /* rubichess */, B14A8BF12528C8DF00B5704C /* ethereal */, @@ -2944,7 +2998,6 @@ B14A8CE52528C8E000B5704C /* engines.cpp */, B14A8D182528C8E000B5704C /* engines-bridging-header.h */, B14A8D192528C8E000B5704C /* engineids.h */, - B14A8D1A2528C8E000B5704C /* stockfish */, ); path = engines; sourceTree = SOURCE_ROOT; @@ -3188,326 +3241,6 @@ path = fathom; sourceTree = ""; }; - B14A8D1A2528C8E000B5704C /* stockfish */ = { - isa = PBXGroup; - children = ( - B14A8D1B2528C8E000B5704C /* endgame.h */, - B14A8D1C2528C8E000B5704C /* thread.cpp */, - B14A8D1D2528C8E000B5704C /* timeman.cpp */, - B14A8D1E2528C8E000B5704C /* ucioption.cpp */, - B14A8D1F2528C8E000B5704C /* nnue */, - B14A8D322528C8E000B5704C /* types.h */, - B14A8D332528C8E000B5704C /* misc.cpp */, - B14A8D342528C8E000B5704C /* tune.h */, - B14A8D352528C8E000B5704C /* misc.h */, - B14A8D362528C8E000B5704C /* bitboard.cpp */, - B14A8D372528C8E000B5704C /* Makefile */, - B14A8D382528C8E000B5704C /* material.h */, - B14A8D392528C8E000B5704C /* psqt.cpp */, - B14A8D3A2528C8E000B5704C /* benchmark.cpp */, - B14A8D3B2528C8E000B5704C /* movepick.cpp */, - B14A8D3C2528C8E000B5704C /* bitboard.h */, - B14A8D3D2528C8E000B5704C /* tune.cpp */, - B14A8D3E2528C8E000B5704C /* pawns.h */, - B14A8D3F2528C8E000B5704C /* syzygy */, - B14A8D422528C8E000B5704C /* bitbase.cpp */, - B14A8D432528C8E000B5704C /* evaluate.cpp */, - B14A8D442528C8E000B5704C /* thread.h */, - B14A8D452528C8E000B5704C /* movegen.h */, - B14A8D462528C8E000B5704C /* search.cpp */, - B14A8D472528C8E000B5704C /* thread_win32_osx.h */, - B14A8D482528C8E000B5704C /* movepick.h */, - B14A8D492528C8E000B5704C /* evaluate.h */, - B14A8D4A2528C8E000B5704C /* pawns.cpp */, - B14A8D4B2528C8E000B5704C /* movegen.cpp */, - B14A8D4C2528C8E000B5704C /* incbin */, - B14A8D4F2528C8E000B5704C /* material.cpp */, - B14A8D502528C8E000B5704C /* tt.cpp */, - B14A8D512528C8E000B5704C /* search.h */, - B14A8D522528C8E000B5704C /* tt.h */, - B14A8D532528C8E000B5704C /* main.cpp */, - B14A8D542528C8E000B5704C /* uci.h */, - B14A8D552528C8E000B5704C /* position.h */, - B14A8D562528C8E000B5704C /* timeman.h */, - B14A8D572528C8E000B5704C /* position.cpp */, - B14A8D582528C8E000B5704C /* uci.cpp */, - B14A8D592528C8E000B5704C /* endgame.cpp */, - ); - path = stockfish; - sourceTree = ""; - }; - B14A8D1F2528C8E000B5704C /* nnue */ = { - isa = PBXGroup; - children = ( - B14A8D202528C8E000B5704C /* evaluate_nnue.cpp */, - B14A8D212528C8E000B5704C /* layers */, - B14A8D252528C8E000B5704C /* nnue_common.h */, - B14A8D262528C8E000B5704C /* nnue_feature_transformer.h */, - B14A8D272528C8E000B5704C /* features */, - B14A8D2D2528C8E000B5704C /* evaluate_nnue.h */, - B14A8D2E2528C8E000B5704C /* architectures */, - B14A8D302528C8E000B5704C /* nnue_accumulator.h */, - B14A8D312528C8E000B5704C /* nnue_architecture.h */, - ); - path = nnue; - sourceTree = ""; - }; - B14A8D212528C8E000B5704C /* layers */ = { - isa = PBXGroup; - children = ( - B14A8D222528C8E000B5704C /* affine_transform.h */, - B14A8D232528C8E000B5704C /* clipped_relu.h */, - B14A8D242528C8E000B5704C /* input_slice.h */, - ); - path = layers; - sourceTree = ""; - }; - B14A8D272528C8E000B5704C /* features */ = { - isa = PBXGroup; - children = ( - B14A8D282528C8E000B5704C /* index_list.h */, - B14A8D292528C8E000B5704C /* features_common.h */, - B14A8D2A2528C8E000B5704C /* half_kp.h */, - B14A8D2B2528C8E000B5704C /* half_kp.cpp */, - B14A8D2C2528C8E000B5704C /* feature_set.h */, - ); - path = features; - sourceTree = ""; - }; - B14A8D2E2528C8E000B5704C /* architectures */ = { - isa = PBXGroup; - children = ( - B14A8D2F2528C8E000B5704C /* halfkp_256x2-32-32.h */, - ); - path = architectures; - sourceTree = ""; - }; - B14A8D3F2528C8E000B5704C /* syzygy */ = { - isa = PBXGroup; - children = ( - B14A8D402528C8E000B5704C /* tbprobe.cpp */, - B14A8D412528C8E000B5704C /* tbprobe.h */, - ); - path = syzygy; - sourceTree = ""; - }; - B14A8D4C2528C8E000B5704C /* incbin */ = { - isa = PBXGroup; - children = ( - B14A8D4D2528C8E000B5704C /* UNLICENCE */, - B14A8D4E2528C8E000B5704C /* incbin.h */, - ); - path = incbin; - sourceTree = ""; - }; - B14F4AE32544D277004DE126 /* igel */ = { - isa = PBXGroup; - children = ( - B14F4AE42544D277004DE126 /* time.h */, - B14F4AE52544D277004DE126 /* bitboards.cpp */, - B14F4AE62544D277004DE126 /* utils.h */, - B14F4AE72544D277004DE126 /* utils.cpp */, - B14F4AE82544D277004DE126 /* igelsearch.h */, - B14F4AE92544D277004DE126 /* moveeval.cpp */, - B14F4AEA2544D277004DE126 /* igeltt.cpp */, - B14F4AEB2544D277004DE126 /* texel.h */, - B14F4AEC2544D277004DE126 /* nnue */, - B14F4AFF2544D277004DE126 /* misc.cpp */, - B14F4B002544D277004DE126 /* misc.h */, - B14F4B012544D277004DE126 /* weights.txt */, - B14F4B022544D277004DE126 /* makefile */, - B14F4B032544D277004DE126 /* history.cpp */, - B14F4B042544D277004DE126 /* eval_params.h */, - B14F4B052544D277004DE126 /* texel.cpp */, - B14F4B062544D277004DE126 /* evaluate.cpp */, - B14F4B072544D277004DE126 /* igelposition.h */, - B14F4B082544D277004DE126 /* history.h */, - B14F4B092544D277004DE126 /* igeltypes.h */, - B14F4B0A2544D277004DE126 /* bitboards.h */, - B14F4B0B2544D277004DE126 /* fathom */, - B14F4B132544D277004DE126 /* igelevaluate.h */, - B14F4B142544D277004DE126 /* notation.cpp */, - B14F4B152544D277004DE126 /* igeltt.h */, - B14F4B162544D277004DE126 /* igelposition.cpp */, - B14F4B172544D277004DE126 /* eval_params.cpp */, - B14F4B182544D277004DE126 /* notation.h */, - B14F4B192544D277004DE126 /* igelsearch.cpp */, - B14F4B1A2544D277004DE126 /* bench.csv */, - B14F4B1B2544D277004DE126 /* moveeval.h */, - B14F4B1C2544D277004DE126 /* moves.cpp */, - B14F4B1D2544D277004DE126 /* main.cpp */, - B14F4B1E2544D277004DE126 /* igeluci.h */, - B14F4B1F2544D277004DE126 /* uci.cpp */, - B14F4B202544D277004DE126 /* time.cpp */, - B14F4B212544D277004DE126 /* moves.h */, - ); - path = igel; - sourceTree = ""; - }; - B14F4AEC2544D277004DE126 /* nnue */ = { - isa = PBXGroup; - children = ( - B14F4AED2544D277004DE126 /* evaluate_nnue.cpp */, - B14F4AEE2544D277004DE126 /* layers */, - B14F4AF22544D277004DE126 /* nnue_common.h */, - B14F4AF32544D277004DE126 /* nnue_feature_transformer.h */, - B14F4AF42544D277004DE126 /* features */, - B14F4AFA2544D277004DE126 /* evaluate_nnue.h */, - B14F4AFB2544D277004DE126 /* architectures */, - B14F4AFD2544D277004DE126 /* nnue_accumulator.h */, - B14F4AFE2544D277004DE126 /* nnue_architecture.h */, - ); - path = nnue; - sourceTree = ""; - }; - B14F4AEE2544D277004DE126 /* layers */ = { - isa = PBXGroup; - children = ( - B14F4AEF2544D277004DE126 /* affine_transform.h */, - B14F4AF02544D277004DE126 /* clipped_relu.h */, - B14F4AF12544D277004DE126 /* input_slice.h */, - ); - path = layers; - sourceTree = ""; - }; - B14F4AF42544D277004DE126 /* features */ = { - isa = PBXGroup; - children = ( - B14F4AF52544D277004DE126 /* index_list.h */, - B14F4AF62544D277004DE126 /* features_common.h */, - B14F4AF72544D277004DE126 /* half_kp.h */, - B14F4AF82544D277004DE126 /* half_kp.cpp */, - B14F4AF92544D277004DE126 /* feature_set.h */, - ); - path = features; - sourceTree = ""; - }; - B14F4AFB2544D277004DE126 /* architectures */ = { - isa = PBXGroup; - children = ( - B14F4AFC2544D277004DE126 /* halfkp_256x2-32-32.h */, - ); - path = architectures; - sourceTree = ""; - }; - B14F4B0B2544D277004DE126 /* fathom */ = { - isa = PBXGroup; - children = ( - B14F4B0C2544D277004DE126 /* tbconfig.h */, - B14F4B0D2544D277004DE126 /* LICENSE */, - B14F4B0E2544D277004DE126 /* igeltbcore.h */, - B14F4B0F2544D277004DE126 /* tbprobe.c */, - B14F4B102544D277004DE126 /* README.md */, - B14F4B112544D277004DE126 /* tbcore.h */, - B14F4B122544D277004DE126 /* tbprobe.h */, - ); - path = fathom; - sourceTree = ""; - }; - B14F4B8E2544D749004DE126 /* igel */ = { - isa = PBXGroup; - children = ( - B14F4B8F2544D749004DE126 /* time.h */, - B14F4B902544D749004DE126 /* bitboards.cpp */, - B14F4B912544D749004DE126 /* utils.h */, - B14F4B922544D749004DE126 /* utils.cpp */, - B14F4B932544D749004DE126 /* igelsearch.h */, - B14F4B942544D749004DE126 /* moveeval.cpp */, - B14F4B952544D74A004DE126 /* igeltt.cpp */, - B14F4B962544D74A004DE126 /* texel.h */, - B14F4B972544D74A004DE126 /* nnue */, - B14F4BAA2544D74A004DE126 /* misc.cpp */, - B14F4BAB2544D74A004DE126 /* misc.h */, - B14F4BAC2544D74A004DE126 /* weights.txt */, - B14F4BAD2544D74A004DE126 /* makefile */, - B14F4BAE2544D74A004DE126 /* history.cpp */, - B14F4BAF2544D74A004DE126 /* eval_params.h */, - B14F4BB02544D74A004DE126 /* texel.cpp */, - B14F4BB12544D74A004DE126 /* evaluate.cpp */, - B14F4BB22544D74A004DE126 /* igelposition.h */, - B14F4BB32544D74A004DE126 /* history.h */, - B14F4BB42544D74A004DE126 /* igeltypes.h */, - B14F4BB52544D74A004DE126 /* bitboards.h */, - B14F4BB62544D74A004DE126 /* fathom */, - B14F4BBE2544D74A004DE126 /* igelevaluate.h */, - B14F4BBF2544D74A004DE126 /* notation.cpp */, - B14F4BC02544D74A004DE126 /* igeltt.h */, - B14F4BC12544D74A004DE126 /* igelposition.cpp */, - B14F4BC22544D74A004DE126 /* eval_params.cpp */, - B14F4BC32544D74A004DE126 /* notation.h */, - B14F4BC42544D74A004DE126 /* igelsearch.cpp */, - B14F4BC52544D74A004DE126 /* bench.csv */, - B14F4BC62544D74A004DE126 /* moveeval.h */, - B14F4BC72544D74A004DE126 /* moves.cpp */, - B14F4BC82544D74A004DE126 /* main.cpp */, - B14F4BC92544D74A004DE126 /* igeluci.h */, - B14F4BCA2544D74A004DE126 /* uci.cpp */, - B14F4BCB2544D74A004DE126 /* time.cpp */, - B14F4BCC2544D74A004DE126 /* moves.h */, - ); - path = igel; - sourceTree = ""; - }; - B14F4B972544D74A004DE126 /* nnue */ = { - isa = PBXGroup; - children = ( - B14F4B982544D74A004DE126 /* evaluate_nnue.cpp */, - B14F4B992544D74A004DE126 /* layers */, - B14F4B9D2544D74A004DE126 /* nnue_common.h */, - B14F4B9E2544D74A004DE126 /* nnue_feature_transformer.h */, - B14F4B9F2544D74A004DE126 /* features */, - B14F4BA52544D74A004DE126 /* evaluate_nnue.h */, - B14F4BA62544D74A004DE126 /* architectures */, - B14F4BA82544D74A004DE126 /* nnue_accumulator.h */, - B14F4BA92544D74A004DE126 /* nnue_architecture.h */, - ); - path = nnue; - sourceTree = ""; - }; - B14F4B992544D74A004DE126 /* layers */ = { - isa = PBXGroup; - children = ( - B14F4B9A2544D74A004DE126 /* affine_transform.h */, - B14F4B9B2544D74A004DE126 /* clipped_relu.h */, - B14F4B9C2544D74A004DE126 /* input_slice.h */, - ); - path = layers; - sourceTree = ""; - }; - B14F4B9F2544D74A004DE126 /* features */ = { - isa = PBXGroup; - children = ( - B14F4BA02544D74A004DE126 /* index_list.h */, - B14F4BA12544D74A004DE126 /* features_common.h */, - B14F4BA22544D74A004DE126 /* half_kp.h */, - B14F4BA32544D74A004DE126 /* half_kp.cpp */, - B14F4BA42544D74A004DE126 /* feature_set.h */, - ); - path = features; - sourceTree = ""; - }; - B14F4BA62544D74A004DE126 /* architectures */ = { - isa = PBXGroup; - children = ( - B14F4BA72544D74A004DE126 /* halfkp_256x2-32-32.h */, - ); - path = architectures; - sourceTree = ""; - }; - B14F4BB62544D74A004DE126 /* fathom */ = { - isa = PBXGroup; - children = ( - B14F4BB72544D74A004DE126 /* tbconfig.h */, - B14F4BB82544D74A004DE126 /* LICENSE */, - B14F4BB92544D74A004DE126 /* igeltbcore.h */, - B14F4BBA2544D74A004DE126 /* tbprobe.c */, - B14F4BBB2544D74A004DE126 /* README.md */, - B14F4BBC2544D74A004DE126 /* tbcore.h */, - B14F4BBD2544D74A004DE126 /* tbprobe.h */, - ); - path = fathom; - sourceTree = ""; - }; B17A8E45254976B400D5C329 /* Lc0Watch */ = { isa = PBXGroup; children = ( @@ -4680,118 +4413,6 @@ path = rubichess; sourceTree = ""; }; - B1B60220254423EB002B3E61 /* stockfish */ = { - isa = PBXGroup; - children = ( - B1B60221254423EB002B3E61 /* endgame.h */, - B1B60222254423EB002B3E61 /* thread.cpp */, - B1B60223254423EB002B3E61 /* timeman.cpp */, - B1B60224254423EB002B3E61 /* ucioption.cpp */, - B1B60225254423EB002B3E61 /* nnue */, - B1B60238254423EB002B3E61 /* types.h */, - B1B60239254423EB002B3E61 /* misc.cpp */, - B1B6023A254423EB002B3E61 /* tune.h */, - B1B6023B254423EB002B3E61 /* misc.h */, - B1B6023C254423EB002B3E61 /* bitboard.cpp */, - B1B6023D254423EB002B3E61 /* Makefile */, - B1B6023E254423EB002B3E61 /* material.h */, - B1B6023F254423EB002B3E61 /* psqt.cpp */, - B1B60240254423EB002B3E61 /* benchmark.cpp */, - B1B60241254423EB002B3E61 /* movepick.cpp */, - B1B60242254423EB002B3E61 /* bitboard.h */, - B1B60243254423EB002B3E61 /* tune.cpp */, - B1B60244254423EB002B3E61 /* pawns.h */, - B1B60245254423EB002B3E61 /* syzygy */, - B1B60248254423EB002B3E61 /* bitbase.cpp */, - B1B60249254423EB002B3E61 /* evaluate.cpp */, - B1B6024A254423EB002B3E61 /* thread.h */, - B1B6024B254423EB002B3E61 /* movegen.h */, - B1B6024C254423EB002B3E61 /* search.cpp */, - B1B6024D254423EB002B3E61 /* thread_win32_osx.h */, - B1B6024E254423EB002B3E61 /* movepick.h */, - B1B6024F254423EB002B3E61 /* evaluate.h */, - B1B60250254423EB002B3E61 /* pawns.cpp */, - B1B60251254423EB002B3E61 /* movegen.cpp */, - B1B60252254423EB002B3E61 /* incbin */, - B1B60255254423EB002B3E61 /* material.cpp */, - B1B60256254423EB002B3E61 /* tt.cpp */, - B1B60257254423EB002B3E61 /* search.h */, - B1B60258254423EB002B3E61 /* tt.h */, - B1B60259254423EB002B3E61 /* main.cpp */, - B1B6025A254423EB002B3E61 /* uci.h */, - B1B6025B254423EB002B3E61 /* position.h */, - B1B6025C254423EB002B3E61 /* timeman.h */, - B1B6025D254423EB002B3E61 /* position.cpp */, - B1B6025E254423EB002B3E61 /* uci.cpp */, - B1B6025F254423EB002B3E61 /* endgame.cpp */, - ); - path = stockfish; - sourceTree = ""; - }; - B1B60225254423EB002B3E61 /* nnue */ = { - isa = PBXGroup; - children = ( - B1B60226254423EB002B3E61 /* evaluate_nnue.cpp */, - B1B60227254423EB002B3E61 /* layers */, - B1B6022B254423EB002B3E61 /* nnue_common.h */, - B1B6022C254423EB002B3E61 /* nnue_feature_transformer.h */, - B1B6022D254423EB002B3E61 /* features */, - B1B60233254423EB002B3E61 /* evaluate_nnue.h */, - B1B60234254423EB002B3E61 /* architectures */, - B1B60236254423EB002B3E61 /* nnue_accumulator.h */, - B1B60237254423EB002B3E61 /* nnue_architecture.h */, - ); - path = nnue; - sourceTree = ""; - }; - B1B60227254423EB002B3E61 /* layers */ = { - isa = PBXGroup; - children = ( - B1B60228254423EB002B3E61 /* affine_transform.h */, - B1B60229254423EB002B3E61 /* clipped_relu.h */, - B1B6022A254423EB002B3E61 /* input_slice.h */, - ); - path = layers; - sourceTree = ""; - }; - B1B6022D254423EB002B3E61 /* features */ = { - isa = PBXGroup; - children = ( - B1B6022E254423EB002B3E61 /* index_list.h */, - B1B6022F254423EB002B3E61 /* features_common.h */, - B1B60230254423EB002B3E61 /* half_kp.h */, - B1B60231254423EB002B3E61 /* half_kp.cpp */, - B1B60232254423EB002B3E61 /* feature_set.h */, - ); - path = features; - sourceTree = ""; - }; - B1B60234254423EB002B3E61 /* architectures */ = { - isa = PBXGroup; - children = ( - B1B60235254423EB002B3E61 /* halfkp_256x2-32-32.h */, - ); - path = architectures; - sourceTree = ""; - }; - B1B60245254423EB002B3E61 /* syzygy */ = { - isa = PBXGroup; - children = ( - B1B60246254423EB002B3E61 /* tbprobe.cpp */, - B1B60247254423EB002B3E61 /* tbprobe.h */, - ); - path = syzygy; - sourceTree = ""; - }; - B1B60252254423EB002B3E61 /* incbin */ = { - isa = PBXGroup; - children = ( - B1B60253254423EB002B3E61 /* UNLICENCE */, - B1B60254254423EB002B3E61 /* incbin.h */, - ); - path = incbin; - sourceTree = ""; - }; B1B60260254423EB002B3E61 /* lc0net */ = { isa = PBXGroup; children = ( @@ -4924,7 +4545,7 @@ B1B6FEC0254423E9002B3E61 /* engines */ = { isa = PBXGroup; children = ( - B14F4B8E2544D749004DE126 /* igel */, + B1044C4C26A666E60032A27F /* stockfish */, B1B6FF06254423E9002B3E61 /* lc0 */, B1B600DC254423EA002B3E61 /* ethereal */, B1B60113254423EA002B3E61 /* defenchess */, @@ -4935,7 +4556,6 @@ B1B6020F254423EB002B3E61 /* rubichess */, B1B6021E254423EB002B3E61 /* engines-bridging-header.h */, B1B6021F254423EB002B3E61 /* engineids.h */, - B1B60220254423EB002B3E61 /* stockfish */, ); path = engines; sourceTree = SOURCE_ROOT; @@ -5607,14 +5227,6 @@ path = nnuenets; sourceTree = SOURCE_ROOT; }; - B1E09142252BEC4300375B24 /* nnuenets */ = { - isa = PBXGroup; - children = ( - B1E09143252BEC4300375B24 /* nn-03744f8d56d8.nnue */, - ); - path = nnuenets; - sourceTree = SOURCE_ROOT; - }; B1E09154252BEC7D00375B24 /* nnuenets */ = { isa = PBXGroup; children = ( @@ -5883,7 +5495,7 @@ buildActionMask = 2147483647; files = ( B14A8B862528C76500B5704C /* alert2.wav in Resources */, - B1E09144252BEC4300375B24 /* nn-03744f8d56d8.nnue in Resources */, + B1044CB026A676A10032A27F /* nn-9e3c6298299a.nnue in Resources */, B1B6060825446321002B3E61 /* 703810.pb.gz in Resources */, B14A8B892528C76500B5704C /* error.wav in Resources */, B14A8B8A2528C76500B5704C /* cap.wav in Resources */, @@ -5892,10 +5504,12 @@ B1C02B092528D43600665CA6 /* LaunchScreen.storyboard in Resources */, B14A8B822528C76500B5704C /* help.html in Resources */, B14A8B852528C76500B5704C /* move.wav in Resources */, + B1044BEB26A65E560032A27F /* UNLICENCE in Resources */, B14A8B842528C76500B5704C /* win.wav in Resources */, B14A8B902528C76500B5704C /* pieces.xcassets in Resources */, B14A8B882528C76500B5704C /* check.wav in Resources */, B14A8D872528C8E000B5704C /* pknet_224x32x1.net in Resources */, + B1044BED26A65E560032A27F /* .depend in Resources */, B14A8B812528C76500B5704C /* credit.html in Resources */, B14A8B772528C76500B5704C /* BsIcon.xcassets in Resources */, ); @@ -5976,6 +5590,7 @@ buildActionMask = 2147483647; files = ( B1B6FE3C2544233E002B3E61 /* BsWatchAssets.xcassets in Resources */, + B1044CB126A676A10032A27F /* nn-9e3c6298299a.nnue in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5983,12 +5598,11 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B1044CB226A6773E0032A27F /* nn-9e3c6298299a.nnue in Resources */, B1B6FEA2254423A8002B3E61 /* move.wav in Resources */, B1B6FE612544237F002B3E61 /* WatchAssets.xcassets in Resources */, - B1B60382254423ED002B3E61 /* nn-03744f8d56d8.nnue in Resources */, B1B6FEA3254423A8002B3E61 /* alert2.wav in Resources */, B1B602E6254423EC002B3E61 /* pknet_224x32x1.net in Resources */, - B14F4BD42544D74A004DE126 /* weights.txt in Resources */, B1B6FEA5254423A8002B3E61 /* check.wav in Resources */, B1B6FEA1254423A8002B3E61 /* win.wav in Resources */, B1B6FEA6254423A8002B3E61 /* error.wav in Resources */, @@ -6207,55 +5821,51 @@ files = ( B14A8DA22528C8E000B5704C /* search.cpp in Sources */, B14A8D7D2528C8E000B5704C /* movegen.c in Sources */, + B1044BF026A65E560032A27F /* position.cpp in Sources */, + B1044BE526A65E560032A27F /* tbprobe.cpp in Sources */, B14A8DD92528C8E000B5704C /* util.c in Sources */, - B14F4B332544D277004DE126 /* eval_params.cpp in Sources */, - B14F4B2F2544D277004DE126 /* tbprobe.c in Sources */, B1026C3D2543268200603CC7 /* factory.cc in Sources */, B1DA819C255DE8530021C5DC /* GExtensions.swift in Sources */, - B14F4B322544D277004DE126 /* igelposition.cpp in Sources */, B14A8B942528C76500B5704C /* GBoard.swift in Sources */, B14A8B932528C76500B5704C /* GAllPieces.swift in Sources */, + B1044BDB26A65E560032A27F /* evaluate_nnue.cpp in Sources */, + B1044BE626A65E560032A27F /* bitbase.cpp in Sources */, B1DA81A1255DE8530021C5DC /* MenuGame.swift in Sources */, - B14F4B342544D277004DE126 /* igelsearch.cpp in Sources */, B14A8D9A2528C8E000B5704C /* bitboard.cpp in Sources */, B14A8B8C2528C76500B5704C /* Game.swift in Sources */, - B14F4B382544D277004DE126 /* uci.cpp in Sources */, B14A8D962528C8E000B5704C /* tbprobe.c in Sources */, - B14A8E112528C8E000B5704C /* movepick.cpp in Sources */, B14A8DAE2528C8E000B5704C /* uci.cpp in Sources */, - B14F4B2D2544D277004DE126 /* evaluate.cpp in Sources */, B14A8DD12528C8E000B5704C /* eval.cpp in Sources */, B1A5A6D92532ED6D0007A258 /* WebView.swift in Sources */, B14A8DAD2528C8E000B5704C /* position.cpp in Sources */, - B14A8E172528C8E000B5704C /* pawns.cpp in Sources */, B1026BFA2543268200603CC7 /* benchmark.cc in Sources */, - B14A8E162528C8E000B5704C /* search.cpp in Sources */, B14A8DC82528C8E000B5704C /* stockfishlib.cpp in Sources */, B14A8DCC2528C8E000B5704C /* bbinit.cpp in Sources */, B1026C062543268200603CC7 /* esc_codes.cc in Sources */, B1A5A6D72532ED6D0007A258 /* OptionUserName.swift in Sources */, - B14A8E092528C8E000B5704C /* ucioption.cpp in Sources */, + B1044BDE26A65E560032A27F /* bitboard.cpp in Sources */, B14A8DE52528C8E000B5704C /* pawn_eval.c in Sources */, B1026BFD2543268200603CC7 /* string.cc in Sources */, + B1044BE926A65E560032A27F /* pawns.cpp in Sources */, B19AE9CA2540D6460037B09E /* uci.cpp in Sources */, B1026C052543268200603CC7 /* random.cc in Sources */, - B14F4B272544D277004DE126 /* half_kp.cpp in Sources */, B14A8B792528C76500B5704C /* ContentView.swift in Sources */, B1026C352543268200603CC7 /* fully_connected_layer.cc in Sources */, B14A8D8D2528C8E000B5704C /* search.c in Sources */, B14A8D9B2528C8E000B5704C /* Makefile in Sources */, + B1044BE226A65E560032A27F /* benchmark.cpp in Sources */, B1026C2C2543268200603CC7 /* uciloop.cc in Sources */, B14A8DD52528C8E000B5704C /* history.c in Sources */, B14A8B6B2528C76500B5704C /* Book.swift in Sources */, + B1044BF226A65E560032A27F /* endgame.cpp in Sources */, B1026C492543268200603CC7 /* timemgr.cc in Sources */, B14A8DAC2528C8E000B5704C /* tb.cpp in Sources */, B19AE9C22540D6460037B09E /* board.cpp in Sources */, - B14A8E1E2528C8E000B5704C /* uci.cpp in Sources */, - B14F4B392544D277004DE126 /* time.cpp in Sources */, B14A8DDD2528C8E000B5704C /* search.c in Sources */, - B14F4B2C2544D277004DE126 /* texel.cpp in Sources */, B14A8DAB2528C8E000B5704C /* eval.cpp in Sources */, B1DA81A3255DE8530021C5DC /* EditView.swift in Sources */, + B1044BE726A65E560032A27F /* evaluate.cpp in Sources */, + B1044BDD26A65E560032A27F /* misc.cpp in Sources */, B14A8DD72528C8E000B5704C /* tables.c in Sources */, B1026C4A2543268200603CC7 /* factory.cc in Sources */, B14A8DDA2528C8E000B5704C /* position.c in Sources */, @@ -6264,10 +5874,9 @@ B14A8D842528C8E000B5704C /* makefile in Sources */, B14A8DE02528C8E000B5704C /* make.c in Sources */, B14A8B912528C76500B5704C /* ArrowView.swift in Sources */, - B14A8E0D2528C8E000B5704C /* bitboard.cpp in Sources */, B14A8B692528C76500B5704C /* Result.swift in Sources */, + B1044BEA26A65E560032A27F /* movegen.cpp in Sources */, B1026C282543268200603CC7 /* syzygy.cc in Sources */, - B14F4B222544D277004DE126 /* bitboards.cpp in Sources */, B1026C4C2543268200603CC7 /* params.cc in Sources */, B1026C2B2543268200603CC7 /* board.cc in Sources */, B14A8DAA2528C8E000B5704C /* main.cpp in Sources */, @@ -6277,36 +5886,30 @@ B14A8DA12528C8E000B5704C /* move.cpp in Sources */, B1026C002543268200603CC7 /* weights_adapter.cc in Sources */, B14A8DDB2528C8E000B5704C /* hash.c in Sources */, + B1044BDC26A65E560032A27F /* half_ka_v2.cpp in Sources */, B14A8B962528C76500B5704C /* GPiece.swift in Sources */, B1A5A6D82532ED6D0007A258 /* OptionColorScheme.swift in Sources */, B1DA819D255DE8530021C5DC /* ContentViewExtension.swift in Sources */, B1026BFE2543268200603CC7 /* numa.cc in Sources */, B1A5A6DC2532ED6D0007A258 /* SoundNameView.swift in Sources */, B1026C022543268200603CC7 /* configfile.cc in Sources */, - B14A8E152528C8E000B5704C /* evaluate.cpp in Sources */, - B14A8E0F2528C8E000B5704C /* psqt.cpp in Sources */, B1026C4D2543268200603CC7 /* version.cc in Sources */, - B14A8E0A2528C8E000B5704C /* evaluate_nnue.cpp in Sources */, + B1044BD926A65E560032A27F /* timeman.cpp in Sources */, B14A8D9F2528C8E000B5704C /* tune.cpp in Sources */, + B1044BDA26A65E560032A27F /* ucioption.cpp in Sources */, B14A8B652528C76500B5704C /* Move.swift in Sources */, B14A8B672528C76500B5704C /* Hist.swift in Sources */, - B14A8E122528C8E000B5704C /* tune.cpp in Sources */, B14A8DA72528C8E000B5704C /* movegen.cpp in Sources */, B14A8DD22528C8E000B5704C /* uci.cpp in Sources */, B14A8D892528C8E000B5704C /* attacks.c in Sources */, - B14F4B232544D277004DE126 /* utils.cpp in Sources */, - B14A8E1F2528C8E000B5704C /* endgame.cpp in Sources */, B14A8D992528C8E000B5704C /* magic.cpp in Sources */, B14A8B6A2528C76500B5704C /* ExtensionLib.swift in Sources */, B1DA819E255DE8530021C5DC /* BannerVC.swift in Sources */, - B14A8E132528C8E000B5704C /* tbprobe.cpp in Sources */, - B14A8E1C2528C8E000B5704C /* main.cpp in Sources */, B1026C322543268200603CC7 /* network_blas.cc in Sources */, - B14F4B242544D277004DE126 /* moveeval.cpp in Sources */, - B14F4B2B2544D277004DE126 /* history.cpp in Sources */, B1026C082543268200603CC7 /* optionsdict.cc in Sources */, B14A8B8D2528C76500B5704C /* UserData.swift in Sources */, B14A8D8B2528C8E000B5704C /* syzygy.c in Sources */, + B1044BE126A65E560032A27F /* psqt.cpp in Sources */, B14A8B8E2528C76500B5704C /* SoundMng.swift in Sources */, B19AE9C92540D6460037B09E /* eval.cpp in Sources */, B1A5A6D62532ED6D0007A258 /* OptionPieceStyle.swift in Sources */, @@ -6324,18 +5927,18 @@ B1026C472543268200603CC7 /* smooth.cc in Sources */, B14A8D802528C8E000B5704C /* network.c in Sources */, B14A8D932528C8E000B5704C /* masks.c in Sources */, - B14F4B362544D277004DE126 /* moves.cpp in Sources */, - B14F4B312544D277004DE126 /* notation.cpp in Sources */, + B1044BE326A65E560032A27F /* movepick.cpp in Sources */, B14A8D982528C8E000B5704C /* thread.cpp in Sources */, + B1044BEE26A65E560032A27F /* tt.cpp in Sources */, B14A8B8B2528C76500B5704C /* BanksiaApp.swift in Sources */, B14A8D902528C8E000B5704C /* movepicker.c in Sources */, B14A8DA62528C8E000B5704C /* target.cpp in Sources */, B19AE9C12540D6460037B09E /* Makefile in Sources */, + B1044BF126A65E560032A27F /* uci.cpp in Sources */, B1026C292543268200603CC7 /* bitboard.cc in Sources */, B14A8D922528C8E000B5704C /* cmdline.c in Sources */, B1026C482543268200603CC7 /* alphazero.cc in Sources */, B14A8DCF2528C8E000B5704C /* search.cpp in Sources */, - B14F4B252544D277004DE126 /* igeltt.cpp in Sources */, B14A8DCA2528C8E000B5704C /* moveorder.cpp in Sources */, B14A8D852528C8E000B5704C /* move.c in Sources */, B14A8D812528C8E000B5704C /* history.c in Sources */, @@ -6346,12 +5949,12 @@ B14A8B6C2528C76500B5704C /* Types.swift in Sources */, B14A8D8A2528C8E000B5704C /* transposition.c in Sources */, B1026BFC2543268200603CC7 /* engine.cc in Sources */, - B14A8E082528C8E000B5704C /* timeman.cpp in Sources */, + B1044BE426A65E560032A27F /* tune.cpp in Sources */, B14A8DA92528C8E000B5704C /* pst.cpp in Sources */, - B14A8E142528C8E000B5704C /* bitbase.cpp in Sources */, B1A5A6D52532ED6D0007A258 /* Benchmark.swift in Sources */, B14A8D7F2528C8E000B5704C /* thread.c in Sources */, B14A8DD02528C8E000B5704C /* hash.cpp in Sources */, + B1044BE826A65E560032A27F /* search.cpp in Sources */, B1026C092543268200603CC7 /* filesystem.posix.cc in Sources */, B1026C2F2543268200603CC7 /* convolution1.cc in Sources */, B14A8DCD2528C8E000B5704C /* board.cpp in Sources */, @@ -6361,27 +5964,21 @@ B14A8B662528C76500B5704C /* ChessBoard.swift in Sources */, B1026C072543268200603CC7 /* logging.cc in Sources */, B14A8DD82528C8E000B5704C /* move_list.c in Sources */, - B14A8E0C2528C8E000B5704C /* misc.cpp in Sources */, - B14A8E0B2528C8E000B5704C /* half_kp.cpp in Sources */, B1DA819F255DE8530021C5DC /* MenuMoveView.swift in Sources */, B1026C372543268200603CC7 /* network_legacy.cc in Sources */, - B14A8E102528C8E000B5704C /* benchmark.cpp in Sources */, - B14A8E072528C8E000B5704C /* thread.cpp in Sources */, - B14A8E1B2528C8E000B5704C /* tt.cpp in Sources */, B1026C432543268200603CC7 /* node.cc in Sources */, + B1044BD826A65E550032A27F /* thread.cpp in Sources */, B1026C462543268200603CC7 /* common.cc in Sources */, B14A8DA02528C8E000B5704C /* see.cpp in Sources */, B14A8DDF2528C8E000B5704C /* perft.c in Sources */, B14A8DCE2528C8E000B5704C /* tbprobe.cpp in Sources */, B1026C382543268200603CC7 /* writer.cc in Sources */, - B14A8E0E2528C8E000B5704C /* Makefile in Sources */, - B14F4B372544D277004DE126 /* main.cpp in Sources */, - B14A8E182528C8E000B5704C /* movegen.cpp in Sources */, B1A5A6D42532ED6D0007A258 /* AboutView.swift in Sources */, B19AE9C62540D6460037B09E /* search.cpp in Sources */, B1026C332543268200603CC7 /* se_unit.cc in Sources */, B14A8DD62528C8E000B5704C /* phash.c in Sources */, B14A8DE72528C8E000B5704C /* gen.c in Sources */, + B1044BEF26A65E560032A27F /* main.cpp in Sources */, B14A8DE92528C8E000B5704C /* engines.cpp in Sources */, B19AE9C02540D6460037B09E /* utils.cpp in Sources */, B19AE9C72540D6460037B09E /* transposition.cpp in Sources */, @@ -6389,36 +5986,33 @@ B1026C392543268200603CC7 /* winograd_filter.cc in Sources */, B1026C3A2543268200603CC7 /* activation.cc in Sources */, B14A8DE12528C8E000B5704C /* main.c in Sources */, - B14F4B262544D277004DE126 /* evaluate_nnue.cpp in Sources */, B14A8D832528C8E000B5704C /* evalcache.c in Sources */, B1A5A6DB2532ED6D0007A258 /* OptionView.swift in Sources */, B1026C3E2543268200603CC7 /* encoder.cc in Sources */, B14A8D8F2528C8E000B5704C /* time.c in Sources */, B1026C312543268200603CC7 /* winograd_convolution3.cc in Sources */, - B14F4B282544D277004DE126 /* misc.cpp in Sources */, B1026C012543268200603CC7 /* protomessage.cc in Sources */, B14A8D912528C8E000B5704C /* tuner.c in Sources */, B14A8D862528C8E000B5704C /* board.c in Sources */, B1DA81A0255DE8530021C5DC /* MoveView.swift in Sources */, - B14A8E1D2528C8E000B5704C /* position.cpp in Sources */, B1026BFF2543268200603CC7 /* optionsparser.cc in Sources */, B14A8D7E2528C8E000B5704C /* zobrist.c in Sources */, B19AE9C42540D6460037B09E /* nnue.cpp in Sources */, B1026C042543268200603CC7 /* commandline.cc in Sources */, B14A8D9D2528C8E000B5704C /* test.cpp in Sources */, B14A8B8F2528C76500B5704C /* EngineData.swift in Sources */, - B14F4B2A2544D277004DE126 /* makefile in Sources */, B19AE9C32540D6460037B09E /* tbprobe.cpp in Sources */, B14A8DD42528C8E000B5704C /* eval.c in Sources */, B1026BFB2543268200603CC7 /* main.cc in Sources */, + B1044BDF26A65E560032A27F /* Makefile in Sources */, B14A8B952528C76500B5704C /* GCell.swift in Sources */, + B1044BEC26A65E560032A27F /* material.cpp in Sources */, B14A8D822528C8E000B5704C /* bitboards.c in Sources */, B14A8D942528C8E000B5704C /* windows.c in Sources */, B14A8D882528C8E000B5704C /* evaluate.c in Sources */, B14A8D9C2528C8E000B5704C /* move_utils.cpp in Sources */, B1DA81A2255DE8530021C5DC /* MenuView.swift in Sources */, B14A8D972528C8E000B5704C /* timecontrol.cpp in Sources */, - B14A8E1A2528C8E000B5704C /* material.cpp in Sources */, B1026C3F2543268200603CC7 /* cache.cc in Sources */, B14A8DE32528C8E000B5704C /* tbprobe.c in Sources */, ); @@ -6675,73 +6269,64 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B1044C8D26A666E70032A27F /* evaluate_nnue.cpp in Sources */, + B1044C8E26A666E70032A27F /* half_ka_v2.cpp in Sources */, + B1044CAA26A6684A0032A27F /* half_ka_v2.h in Sources */, B1B602EE254423EC002B3E61 /* time.c in Sources */, - B1B60376254423ED002B3E61 /* evaluate.cpp in Sources */, + B1044C8A26A666E70032A27F /* thread.cpp in Sources */, + B1044C9326A666E70032A27F /* psqt.cpp in Sources */, B1B602F7254423EC002B3E61 /* thread.cpp in Sources */, B1B60364254423ED002B3E61 /* transposition.cpp in Sources */, B1B60366254423ED002B3E61 /* eval.cpp in Sources */, B1B60360254423ED002B3E61 /* tbprobe.cpp in Sources */, B1B6FEA8254423A8002B3E61 /* ArrowView.swift in Sources */, - B1B6036F254423ED002B3E61 /* Makefile in Sources */, B1B60309254423EC002B3E61 /* main.cpp in Sources */, - B1B60375254423ED002B3E61 /* bitbase.cpp in Sources */, B1B602D5254423EC002B3E61 /* smooth.cc in Sources */, - B1B6032A254423ED002B3E61 /* Makefile in Sources */, + B1044C9726A666E70032A27F /* tbprobe.cpp in Sources */, B1B60367254423ED002B3E61 /* uci.cpp in Sources */, - B1B6036E254423ED002B3E61 /* bitboard.cpp in Sources */, + B1044C8F26A666E70032A27F /* misc.cpp in Sources */, B1B60340254423ED002B3E61 /* main.c in Sources */, + B1044C8C26A666E70032A27F /* ucioption.cpp in Sources */, B1B6035D254423ED002B3E61 /* utils.cpp in Sources */, - B14F4BD32544D74A004DE126 /* misc.cpp in Sources */, B1B60288254423EB002B3E61 /* benchmark.cc in Sources */, + B1044C8B26A666E70032A27F /* timeman.cpp in Sources */, B1B602DD254423EC002B3E61 /* zobrist.c in Sources */, B1B6028E254423EB002B3E61 /* weights_adapter.cc in Sources */, B1B602C9254423EC002B3E61 /* loader.cc in Sources */, B1B60296254423EC002B3E61 /* optionsdict.cc in Sources */, - B1B6037C254423ED002B3E61 /* tt.cpp in Sources */, B1B6030B254423EC002B3E61 /* tb.cpp in Sources */, B1B6030A254423EC002B3E61 /* eval.cpp in Sources */, B1B60295254423EC002B3E61 /* logging.cc in Sources */, - B1B6036A254423ED002B3E61 /* ucioption.cpp in Sources */, - B1B60377254423ED002B3E61 /* search.cpp in Sources */, B1B6034A254423ED002B3E61 /* engines.cpp in Sources */, - B14F4BDE2544D74A004DE126 /* eval_params.cpp in Sources */, - B1B602E3254423EC002B3E61 /* makefile in Sources */, - B14F4BE42544D74A004DE126 /* time.cpp in Sources */, B1B602F8254423EC002B3E61 /* magic.cpp in Sources */, B1B602CD254423EC002B3E61 /* cache.cc in Sources */, B1B602FB254423EC002B3E61 /* move_utils.cpp in Sources */, B1B60328254423ED002B3E61 /* common.cpp in Sources */, + B1044CA126A666E70032A27F /* main.cpp in Sources */, B1B602D7254423EC002B3E61 /* timemgr.cc in Sources */, B1B602D6254423EC002B3E61 /* alphazero.cc in Sources */, B1B60330254423ED002B3E61 /* eval.cpp in Sources */, - B1B6035E254423ED002B3E61 /* Makefile in Sources */, B1B602CC254423EC002B3E61 /* encoder.cc in Sources */, B1B6033A254423ED002B3E61 /* hash.c in Sources */, - B14F4BDF2544D74A004DE126 /* igelsearch.cpp in Sources */, B1B60301254423EC002B3E61 /* search.cpp in Sources */, B1B6030E254423EC002B3E61 /* params.cpp in Sources */, + B1044CA426A666E70032A27F /* endgame.cpp in Sources */, B1B602C3254423EC002B3E61 /* fully_connected_layer.cc in Sources */, B1B602D9254423EC002B3E61 /* legacy.cc in Sources */, B1B60294254423EC002B3E61 /* esc_codes.cc in Sources */, - B14F4BD02544D74A004DE126 /* igeltt.cpp in Sources */, B1B60335254423ED002B3E61 /* phash.c in Sources */, B1B602D4254423EC002B3E61 /* common.cc in Sources */, - B1B60380254423ED002B3E61 /* endgame.cpp in Sources */, - B1B60373254423ED002B3E61 /* tune.cpp in Sources */, - B1B6036C254423ED002B3E61 /* half_kp.cpp in Sources */, B1B6032C254423ED002B3E61 /* board.cpp in Sources */, + B1044C9C26A666E70032A27F /* movegen.cpp in Sources */, B1B602F1254423EC002B3E61 /* cmdline.c in Sources */, B1B602DF254423EC002B3E61 /* network.c in Sources */, B1B602F3254423EC002B3E61 /* windows.c in Sources */, + B1044C9626A666E70032A27F /* tune.cpp in Sources */, B1B602FF254423EC002B3E61 /* see.cpp in Sources */, - B1B6036D254423ED002B3E61 /* misc.cpp in Sources */, - B14F4BD62544D74A004DE126 /* history.cpp in Sources */, B1B602E5254423EC002B3E61 /* board.c in Sources */, - B14F4BD72544D74A004DE126 /* texel.cpp in Sources */, B1B6032F254423ED002B3E61 /* hash.cpp in Sources */, B1B6FEB2254423A8002B3E61 /* ChessContentExtension.swift in Sources */, B1B6FE9B254423A8002B3E61 /* Piece.swift in Sources */, - B1B60372254423ED002B3E61 /* movepick.cpp in Sources */, B1B6FE662544237F002B3E61 /* WatchOptionView.swift in Sources */, B1B602D0254423EC002B3E61 /* version.inc in Sources */, B1B60331254423ED002B3E61 /* uci.cpp in Sources */, @@ -6750,31 +6335,31 @@ B1B60333254423ED002B3E61 /* eval.c in Sources */, B1B60297254423EC002B3E61 /* filesystem.posix.cc in Sources */, B1B60329254423ED002B3E61 /* moveorder.cpp in Sources */, - B14F4BD12544D74A004DE126 /* evaluate_nnue.cpp in Sources */, B1B60327254423ED002B3E61 /* stockfishlib.cpp in Sources */, B1B6FE9E254423A8002B3E61 /* Book.swift in Sources */, - B14F4BDD2544D74A004DE126 /* igelposition.cpp in Sources */, - B14F4BD22544D74A004DE126 /* half_kp.cpp in Sources */, B1B6FE9F254423A8002B3E61 /* Types.swift in Sources */, B1B602DB254423EC002B3E61 /* version.cc in Sources */, B1B6FEA9254423A8002B3E61 /* GAllPieces.swift in Sources */, B1B60291254423EC002B3E61 /* histogram.cc in Sources */, B1B60338254423ED002B3E61 /* util.c in Sources */, B1B60300254423EC002B3E61 /* move.cpp in Sources */, + B1044C9026A666E70032A27F /* bitboard.cpp in Sources */, B1B602F5254423EC002B3E61 /* tbprobe.c in Sources */, B1B602DC254423EC002B3E61 /* movegen.c in Sources */, - B14F4BE12544D74A004DE126 /* moves.cpp in Sources */, B1B6032D254423ED002B3E61 /* tbprobe.cpp in Sources */, - B14F4BCD2544D74A004DE126 /* bitboards.cpp in Sources */, + B1044C9E26A666E70032A27F /* material.cpp in Sources */, + B1044C9526A666E70032A27F /* movepick.cpp in Sources */, B1B6FE99254423A8002B3E61 /* ChessBoard.swift in Sources */, B1B6FE682544237F002B3E61 /* WatchEngineList.swift in Sources */, B1B6035F254423ED002B3E61 /* board.cpp in Sources */, B1B60306254423EC002B3E61 /* movegen.cpp in Sources */, + B1044CA026A666E70032A27F /* tt.cpp in Sources */, B1B602D2254423EC002B3E61 /* search.cc in Sources */, - B1B6036B254423ED002B3E61 /* evaluate_nnue.cpp in Sources */, B1B602EB254423EC002B3E61 /* uci.c in Sources */, B1B602D8254423EC002B3E61 /* factory.cc in Sources */, B1B60293254423EC002B3E61 /* random.cc in Sources */, + B1044CA326A666E70032A27F /* uci.cpp in Sources */, + B1044C9B26A666E70032A27F /* pawns.cpp in Sources */, B1B602EF254423EC002B3E61 /* movepicker.c in Sources */, B1B6028B254423EB002B3E61 /* string.cc in Sources */, B1B6033B254423ED002B3E61 /* uci.c in Sources */, @@ -6790,14 +6375,10 @@ B1B602C0254423EC002B3E61 /* network_blas.cc in Sources */, B1B6028D254423EB002B3E61 /* optionsparser.cc in Sources */, B1B6FEAF254423A8002B3E61 /* SoundMng.swift in Sources */, - B1B6037F254423ED002B3E61 /* uci.cpp in Sources */, - B1B60378254423ED002B3E61 /* pawns.cpp in Sources */, B1B60289254423EB002B3E61 /* main.cc in Sources */, B1B6FEB0254423A8002B3E61 /* EngineData.swift in Sources */, B1B6028C254423EB002B3E61 /* numa.cc in Sources */, B1B602DA254423EC002B3E61 /* params.cc in Sources */, - B14F4BD82544D74A004DE126 /* evaluate.cpp in Sources */, - B1B60379254423ED002B3E61 /* movegen.cpp in Sources */, B1B60363254423ED002B3E61 /* search.cpp in Sources */, B1B6030D254423EC002B3E61 /* uci.cpp in Sources */, B1B6033F254423ED002B3E61 /* make.c in Sources */, @@ -6810,60 +6391,51 @@ B1B60361254423ED002B3E61 /* nnue.cpp in Sources */, B1B6FE9D254423A8002B3E61 /* ExtensionLib.swift in Sources */, B1B6FEAC254423A8002B3E61 /* GPiece.swift in Sources */, - B1B60369254423ED002B3E61 /* timeman.cpp in Sources */, B1B6FE98254423A8002B3E61 /* Move.swift in Sources */, - B14F4BD52544D74A004DE126 /* makefile in Sources */, - B1B60370254423ED002B3E61 /* psqt.cpp in Sources */, B1B6FE632544237F002B3E61 /* WatchGameSetup.swift in Sources */, + B1044CA226A666E70032A27F /* position.cpp in Sources */, B1B602F6254423EC002B3E61 /* timecontrol.cpp in Sources */, - B1B6037E254423ED002B3E61 /* position.cpp in Sources */, + B1044C9426A666E70032A27F /* benchmark.cpp in Sources */, B1B602C1254423EC002B3E61 /* se_unit.cc in Sources */, + B1044C9826A666E70032A27F /* bitbase.cpp in Sources */, B1B6FE672544237F002B3E61 /* WatchContentViewExtensions.swift in Sources */, B1B602F2254423EC002B3E61 /* masks.c in Sources */, - B14F4BE22544D74A004DE126 /* main.cpp in Sources */, B1B602F0254423EC002B3E61 /* tuner.c in Sources */, B1B60334254423ED002B3E61 /* history.c in Sources */, - B1B602FA254423EC002B3E61 /* Makefile in Sources */, B1B6032E254423ED002B3E61 /* search.cpp in Sources */, B1B60344254423ED002B3E61 /* pawn_eval.c in Sources */, B1B602D3254423EC002B3E61 /* stoppers.cc in Sources */, B1B602BF254423EC002B3E61 /* winograd_convolution3.cc in Sources */, - B14F4BCF2544D74A004DE126 /* moveeval.cpp in Sources */, B1B60339254423ED002B3E61 /* position.c in Sources */, + B1044C9A26A666E70032A27F /* search.cpp in Sources */, B1B6032B254423ED002B3E61 /* bbinit.cpp in Sources */, B1B602B7254423EC002B3E61 /* bitboard.cc in Sources */, B1B6FEAE254423A8002B3E61 /* UserData.swift in Sources */, + B1044C9926A666E70032A27F /* evaluate.cpp in Sources */, B1B6FEAD254423A8002B3E61 /* Game.swift in Sources */, B1B602B6254423EC002B3E61 /* syzygy.cc in Sources */, B1B602C6254423EC002B3E61 /* writer.cc in Sources */, B1B6FE642544237F002B3E61 /* ComplicationController.swift in Sources */, B1B602EA254423EC002B3E61 /* syzygy.c in Sources */, B1B6028A254423EB002B3E61 /* engine.cc in Sources */, - B1B60374254423ED002B3E61 /* tbprobe.cpp in Sources */, B1B60347254423ED002B3E61 /* bitboard.c in Sources */, B1B6FEAA254423A8002B3E61 /* GBoard.swift in Sources */, B1B602FE254423EC002B3E61 /* tune.cpp in Sources */, - B14F4BE32544D74A004DE126 /* uci.cpp in Sources */, B1B602C7254423EC002B3E61 /* winograd_filter.cc in Sources */, B1B602E4254423EC002B3E61 /* move.c in Sources */, B1B602B9254423EC002B3E61 /* board.cc in Sources */, - B1B60371254423ED002B3E61 /* benchmark.cpp in Sources */, B1B60346254423ED002B3E61 /* gen.c in Sources */, B1B602C5254423EC002B3E61 /* network_legacy.cc in Sources */, B1B60345254423ED002B3E61 /* tablebases.c in Sources */, - B14F4BDA2544D74A004DE126 /* tbprobe.c in Sources */, B1B602FC254423EC002B3E61 /* test.cpp in Sources */, - B14F4BDC2544D74A004DE126 /* notation.cpp in Sources */, B1B602DE254423EC002B3E61 /* thread.c in Sources */, B1B60336254423ED002B3E61 /* tables.c in Sources */, B1B602CB254423EC002B3E61 /* factory.cc in Sources */, B1B60292254423EC002B3E61 /* commandline.cc in Sources */, B1B60308254423EC002B3E61 /* pst.cpp in Sources */, - B14F4BCE2544D74A004DE126 /* utils.cpp in Sources */, B1B602C8254423EC002B3E61 /* activation.cc in Sources */, B1B602E0254423EC002B3E61 /* history.c in Sources */, B1B60304254423EC002B3E61 /* tbprobe.cpp in Sources */, - B1B6037B254423ED002B3E61 /* material.cpp in Sources */, B1B602E2254423EC002B3E61 /* evalcache.c in Sources */, B1B602E8254423EC002B3E61 /* attacks.c in Sources */, B1B60365254423ED002B3E61 /* main.cpp in Sources */, @@ -6873,11 +6445,9 @@ B1B60290254423EC002B3E61 /* configfile.cc in Sources */, B1B602EC254423EC002B3E61 /* search.c in Sources */, B1B6FE9A254423A8002B3E61 /* Hist.swift in Sources */, - B1B60368254423ED002B3E61 /* thread.cpp in Sources */, B1B6033C254423ED002B3E61 /* search.c in Sources */, B1B60332254423ED002B3E61 /* move_eval.c in Sources */, B1B6FEAB254423A8002B3E61 /* GCell.swift in Sources */, - B1B6037D254423ED002B3E61 /* main.cpp in Sources */, B1B60342254423ED002B3E61 /* tbprobe.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -7158,8 +6728,8 @@ "-DNO_PEXT", "-DEVAL_NNUE=1", "-DNNUE_EMBEDDING_OFF", - "-DUSE_POPCNT", "-DUSE_NEON", + "-DUSE_POPCNT", ); OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_SWIFT_FLAGS = "-DSHOW_ADS"; @@ -7204,8 +6774,8 @@ "-DNO_PEXT", "-DEVAL_NNUE=1", "-DNNUE_EMBEDDING_OFF", - "-DUSE_POPCNT", "-DUSE_NEON", + "-DUSE_POPCNT", ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", diff --git a/BanksiaIOS.xcodeproj/project.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/UserInterfaceState.xcuserstate b/BanksiaIOS.xcodeproj/project.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/UserInterfaceState.xcuserstate index d0a9f5a..ac0d924 100644 Binary files a/BanksiaIOS.xcodeproj/project.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/UserInterfaceState.xcuserstate and b/BanksiaIOS.xcodeproj/project.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/BanksiaWatch (Complication).xcscheme b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/BanksiaWatch (Complication).xcscheme index aa0b236..599739c 100644 --- a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/BanksiaWatch (Complication).xcscheme +++ b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/BanksiaWatch (Complication).xcscheme @@ -55,8 +55,10 @@ debugServiceExtension = "internal" allowLocationSimulation = "YES" launchAutomaticallySubstyle = "32"> - + - + - + - + + + + + diff --git a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/BanksiaWatch.xcscheme b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/BanksiaWatch.xcscheme index cce4cb7..1383cfe 100644 --- a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/BanksiaWatch.xcscheme +++ b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/BanksiaWatch.xcscheme @@ -54,8 +54,10 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + - + - + + + + + diff --git a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/Lc0Watch (Complication).xcscheme b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/Lc0Watch (Complication).xcscheme index f2c04e9..723debf 100644 --- a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/Lc0Watch (Complication).xcscheme +++ b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/Lc0Watch (Complication).xcscheme @@ -55,8 +55,10 @@ debugServiceExtension = "internal" allowLocationSimulation = "YES" launchAutomaticallySubstyle = "32"> - + - + - + - + + + + + diff --git a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/Lc0Watch.xcscheme b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/Lc0Watch.xcscheme index 389cbb6..3108e50 100644 --- a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/Lc0Watch.xcscheme +++ b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/Lc0Watch.xcscheme @@ -54,8 +54,10 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + - + - + + + + + diff --git a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/StockfishWatch (Complication).xcscheme b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/StockfishWatch (Complication).xcscheme index 5822a67..a82995d 100644 --- a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/StockfishWatch (Complication).xcscheme +++ b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/StockfishWatch (Complication).xcscheme @@ -55,8 +55,10 @@ debugServiceExtension = "internal" allowLocationSimulation = "YES" launchAutomaticallySubstyle = "32"> - + - + - + - + + + + + diff --git a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/StockfishWatch.xcscheme b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/StockfishWatch.xcscheme index 75e6e0f..3d9c52e 100644 --- a/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/StockfishWatch.xcscheme +++ b/BanksiaIOS.xcodeproj/xcshareddata/xcschemes/StockfishWatch.xcscheme @@ -54,8 +54,10 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + - + - + + + + + diff --git a/BanksiaIOS.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/UserInterfaceState.xcuserstate b/BanksiaIOS.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/UserInterfaceState.xcuserstate index 4c4e08d..a4be607 100644 Binary files a/BanksiaIOS.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/UserInterfaceState.xcuserstate and b/BanksiaIOS.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/BanksiaIOS.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/BanksiaIOS.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 05f3eaf..d958a16 100644 --- a/BanksiaIOS.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/BanksiaIOS.xcworkspace/xcuserdata/nguyenpham.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -128,7 +128,7 @@ endingColumnNumber = "9223372036854775807" startingLineNumber = "445" endingLineNumber = "445" - landmarkName = "doCopyEmail(copy:)" + landmarkName = "presentMailCompose(subject:body:attach:)" landmarkType = "7"> @@ -756,5 +756,101 @@ landmarkType = "7"> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BanksiaIOS/model/EngineData.swift b/BanksiaIOS/model/EngineData.swift index 9099fac..400e4df 100644 --- a/BanksiaIOS/model/EngineData.swift +++ b/BanksiaIOS/model/EngineData.swift @@ -18,10 +18,10 @@ import SwiftUI -let version_stockfish = "13 dev" +let version_stockfish = "14 dev" let version_lc0 = "0.26.3" -let network_nnue = "nn-03744f8d56d8.nnue" +let network_nnue = "nn-9e3c6298299a.nnue" let network_lc0 = "703810.pb.gz" struct EngineInfo : Hashable { @@ -52,8 +52,8 @@ let rubichessEngineInfo = EngineInfo(name: "RubiChess", shortName: "Rb", version let laserEngineInfo = EngineInfo(name: "Laser", shortName: "Ls", version: "1.6", author: "Jeffrey An, Michael An", frc: true, idNumb: laser, elo: 3288, nnue: false, bench: false) -let igelEngineInfo = EngineInfo(name: "Igel", shortName: "Ig", version: "2.6", author: "Medvedev, Shcherbyna", - frc: true, idNumb: igel, elo: 3240, nnue: true, bench: false) +//let igelEngineInfo = EngineInfo(name: "Igel", shortName: "Ig", version: "2.6", author: "Medvedev, Shcherbyna", +// frc: true, idNumb: igel, elo: 3240, nnue: true, bench: false) //let nemorinoEngineInfo = EngineInfo(name: "Nemorino", shortName: "Nm", version: "6", author: "Christian Günther", // frc: true, idNumb: nemorino, elo: 3149, nnue: true, bench: false) @@ -76,7 +76,7 @@ final class EngineData: ObservableObject { let allEngines = [ stockfishEngineInfo, lc0EngineInfo, - igelEngineInfo, +// igelEngineInfo, // nemorinoEngineInfo ] #else @@ -88,7 +88,7 @@ final class EngineData: ObservableObject { rubichessEngineInfo, laserEngineInfo, defenchessEngineInfo, - igelEngineInfo, +// igelEngineInfo, // nemorinoEngineInfo, // fruitEngineInfo ] diff --git a/BanksiaIOS/model/Game.swift b/BanksiaIOS/model/Game.swift index 01fb094..4117b72 100644 --- a/BanksiaIOS/model/Game.swift +++ b/BanksiaIOS/model/Game.swift @@ -618,12 +618,12 @@ final class Game: ObservableObject { } func computerGo() { + self.expectingBestmove = true DispatchQueue.global(qos: .userInitiated).async { self.initCurrentEngine(); self.sendToEngine(cmd: self.uciPosition()) self.sendToEngine(cmd: self.uciGo()) } - self.expectingBestmove = true } func benchmark(core: Int) { diff --git a/BanksiaIOS/options-ios/AboutView.swift b/BanksiaIOS/options-ios/AboutView.swift index 433d090..343ab3e 100644 --- a/BanksiaIOS/options-ios/AboutView.swift +++ b/BanksiaIOS/options-ios/AboutView.swift @@ -23,7 +23,7 @@ struct AboutView: View { VStack { Text("Banksia GUI for iOS, version \(ContentView.version)") .bold() - Text("by Nguyen Pham, 2020") + Text("by Nguyen Pham, 2020, 2021") .italic() #if STOCKFISHONLY Text("Stockfish NNUE, version \(version_stockfish)") diff --git a/BanksiaIOS/options-ios/OptionView.swift b/BanksiaIOS/options-ios/OptionView.swift index a8c5601..0d484ce 100644 --- a/BanksiaIOS/options-ios/OptionView.swift +++ b/BanksiaIOS/options-ios/OptionView.swift @@ -158,7 +158,7 @@ struct OptionView: View { } .navigationBarTitle("Options", displayMode: .inline) .onAppear { - self.isNavigationBarHidden = false +// self.isNavigationBarHidden = false } .onDisappear() { self.userData.write() diff --git a/BanksiaIOS/views-ios/ContentViewExtension.swift b/BanksiaIOS/views-ios/ContentViewExtension.swift index 2a8bc0a..5a5e67b 100644 --- a/BanksiaIOS/views-ios/ContentViewExtension.swift +++ b/BanksiaIOS/views-ios/ContentViewExtension.swift @@ -46,10 +46,10 @@ extension ContentView { } .background(Color.white) } - + /// Board & pieces create_BoardView(size: size) - + /// Moves, controllers if !flipSize { if editing { @@ -224,7 +224,7 @@ extension ContentView { case .cancel: break - + case .paste: if let string = UIPasteboard.general.string, !string.isEmpty { game.paste(string: string) diff --git a/BanksiaIOS/views-ios/MenuGame.swift b/BanksiaIOS/views-ios/MenuGame.swift index 8b39270..549798a 100644 --- a/BanksiaIOS/views-ios/MenuGame.swift +++ b/BanksiaIOS/views-ios/MenuGame.swift @@ -209,7 +209,7 @@ struct GameSetup: View { .navigationBarTitle("Level", displayMode: .inline) .onAppear { nodeString = "\(game.timer_nodes)" - self.isNavigationBarHidden = false +// self.isNavigationBarHidden = false } .onDisappear() { self.game.write() diff --git a/BanksiaIOS/views/ChessContentExtension.swift b/BanksiaIOS/views/ChessContentExtension.swift index 4d01007..e2a71f7 100644 --- a/BanksiaIOS/views/ChessContentExtension.swift +++ b/BanksiaIOS/views/ChessContentExtension.swift @@ -226,7 +226,7 @@ extension ContentView { assert(game.chessBoard.histList.last?.es.elapsedInMillisecond == moveElapseInMillisecond) } - setupForNewMove() + setupForNewMove(autoGo: true) playSound(move: game.chessBoard.histList.last?.move ?? MoveFull.illegalMoveFull, sanString: game.chessBoard.histList.last?.sanString ?? "") return true } @@ -237,16 +237,17 @@ extension ContentView { func newGame() { if game.expectingBestmove { - return delayTask(.new) + game.sendUciStop() + return delayTask(.new) } computingTask.removeAll() game.expectingBestmove = false computingTask.removeAll() game.newGame() - newGame_complete() + newGame_complete(autoGo: true) } - func newGame_complete() { + func newGame_complete(autoGo: Bool) { hasEngineOutput = false pvString = "" scoreString = "" @@ -261,7 +262,7 @@ extension ContentView { nodes = 0 nps = 0 - setupForNewMove() + setupForNewMove(autoGo: autoGo) } func computerGo() { @@ -316,7 +317,7 @@ extension ContentView { let moveElapseInMillisecond = Int((CFAbsoluteTimeGetCurrent() - startMoveTime) * 1000) game.takeBack(moveElapseInMillisecond: moveElapseInMillisecond) - setupForNewMove() + setupForNewMove(autoGo: true) game.savePGNGame() // game.chessBoard.printBoard() } diff --git a/BanksiaIOS/views/ContentView.swift b/BanksiaIOS/views/ContentView.swift index 810fc6b..2e607a2 100644 --- a/BanksiaIOS/views/ContentView.swift +++ b/BanksiaIOS/views/ContentView.swift @@ -21,7 +21,7 @@ import UIKit struct ContentView: View { - static let version = "1.0" + static let version = "1.1" @EnvironmentObject var userData: UserData @EnvironmentObject var game: Game @@ -194,13 +194,13 @@ struct ContentView: View { self.initDone = true game.variantChanged = variantChanged - newGame_complete() + newGame_complete(autoGo: false) } func variantChanged(varEnum: VarEnum) { switch varEnum { case .turn: - turnChanged() + turnChanged(autoGo: true) case .analysicOnFly: print("main analysicOnFly", self.game.analysicOnFlyMode) @@ -361,22 +361,22 @@ struct ContentView: View { promotionDest = -1 } - func setupForNewMove() { + func setupForNewMove(autoGo: Bool) { legalSet.removeAll() clearAllMarks() showingPopup_promotion = false - turnChanged() + turnChanged(autoGo: autoGo) } - func turnChanged() { + func turnChanged(autoGo: Bool) { game.setupClocksBeforeThinking(newGame: game.chessBoard.histList.isEmpty) startMoveTime = CFAbsoluteTimeGetCurrent() let isHumanTurn = game.analysicOnFlyMode || game.isHumanTurn() humanTurn = game.chessBoard.result.isNone() && isHumanTurn ? self.game.chessBoard.side : Side.none - if !isHumanTurn || game.analysicOnFlyMode { + if autoGo && (!isHumanTurn || game.analysicOnFlyMode) { computerGo() } } diff --git a/README.md b/README.md index e2ccf44..3735420 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,11 @@ Typically a chess engine can’t work independently (as an independent app) unde Some chess engines are integrated and can run well under Banksia GUI. Their information is in Licenses/Credits. +## Compile +### Simulators +Simulators can't be compiled with flag -DUSE_NEON. Remove that flag for both iOS and AppleWatch before compiling. + + ## Release on AppStore We planned to publish BanksiaGUI as several apps, based on which engines released with. For example, Lc0 would be Lc0 engine + BanksiaGUI. However, AppStore did not allow to release some apps with quite similar functions. Thus we have published only Banksia GUI app with all available engines at: https://github.com/nguyenpham/banksiagui-ios diff --git a/engines/engineids.h b/engines/engineids.h index a4364ca..38bc1dc 100644 --- a/engines/engineids.h +++ b/engines/engineids.h @@ -27,7 +27,7 @@ enum { rubichess, laser, defenchess, - igel, +// igel, // nemorino, // fruit }; diff --git a/engines/engines.cpp b/engines/engines.cpp index 4dd5710..7e91aee 100644 --- a/engines/engines.cpp +++ b/engines/engines.cpp @@ -115,8 +115,8 @@ void laser_uci_cmd(const char* str); #endif -void igel_initialize(); -void igel_uci_cmd(const char* str); +//void igel_initialize(); +//void igel_uci_cmd(const char* str); //void nemorino_initialize(); //void nemorino_uci_cmd(const char* str); @@ -177,10 +177,10 @@ void engine_initialize(int eid, int coreNumber, int skillLevel, int nnueMode) #if !(defined STOCKFISHONLY || defined LC0ONLY) - case igel: - igel_initialize(); /// Threads, Hash, Ponder, EvalFile, Skill level - sendOptionNNUE(eid); - break; +// case igel: +// igel_initialize(); /// Threads, Hash, Ponder, EvalFile, Skill level +// sendOptionNNUE(eid); +// break; // case nemorino: // nemorino_initialize(); /// Threads, Hash, Ponder, EvalFile @@ -287,9 +287,9 @@ void engine_cmd(int eid, const char *cmd) #endif /// #ifndef NNONLY - case igel: - igel_uci_cmd(cmd); - break; +// case igel: +// igel_uci_cmd(cmd); +// break; // case nemorino: // nemorino_uci_cmd(cmd); diff --git a/engines/igel/nnue/architectures/halfkp_256x2-32-32.h b/engines/igel/igelnnue/architectures/halfkp_256x2-32-32.h similarity index 100% rename from engines/igel/nnue/architectures/halfkp_256x2-32-32.h rename to engines/igel/igelnnue/architectures/halfkp_256x2-32-32.h diff --git a/engines/igel/nnue/evaluate_nnue.cpp b/engines/igel/igelnnue/evaluate_nnue.cpp similarity index 100% rename from engines/igel/nnue/evaluate_nnue.cpp rename to engines/igel/igelnnue/evaluate_nnue.cpp diff --git a/engines/igel/nnue/evaluate_nnue.h b/engines/igel/igelnnue/evaluate_nnue.h similarity index 100% rename from engines/igel/nnue/evaluate_nnue.h rename to engines/igel/igelnnue/evaluate_nnue.h diff --git a/engines/igel/nnue/features/feature_set.h b/engines/igel/igelnnue/features/feature_set.h similarity index 100% rename from engines/igel/nnue/features/feature_set.h rename to engines/igel/igelnnue/features/feature_set.h diff --git a/engines/igel/nnue/features/features_common.h b/engines/igel/igelnnue/features/features_common.h similarity index 100% rename from engines/igel/nnue/features/features_common.h rename to engines/igel/igelnnue/features/features_common.h diff --git a/engines/igel/nnue/features/half_kp.cpp b/engines/igel/igelnnue/features/half_kp.cpp similarity index 99% rename from engines/igel/nnue/features/half_kp.cpp rename to engines/igel/igelnnue/features/half_kp.cpp index 085179d..d842ba7 100644 --- a/engines/igel/nnue/features/half_kp.cpp +++ b/engines/igel/igelnnue/features/half_kp.cpp @@ -41,7 +41,7 @@ namespace Eval::IGEL_NNUE::Features { const PieceId target = (AssociatedKing == Side::kFriend) ? static_cast(PIECE_ID_KING + perspective) : static_cast(PIECE_ID_KING + ~perspective); - *sq_target_k = static_cast(((*pieces)[target] - PS_W_KING) % SQUARE_NB); + *sq_target_k = static_cast(((*pieces)[target] - PS_W_KING) % IGEL_SQUARE_NB); } // Get a list of indices for active features diff --git a/engines/igel/nnue/features/half_kp.h b/engines/igel/igelnnue/features/half_kp.h similarity index 96% rename from engines/igel/nnue/features/half_kp.h rename to engines/igel/igelnnue/features/half_kp.h index c3f71f1..0f60bab 100644 --- a/engines/igel/nnue/features/half_kp.h +++ b/engines/igel/igelnnue/features/half_kp.h @@ -39,7 +39,7 @@ namespace Eval::IGEL_NNUE::Features { 0x5D69D5B9u ^ (AssociatedKing == Side::kFriend); // Number of feature dimensions static constexpr IndexType kDimensions = - static_cast(SQUARE_NB) * static_cast(PS_END); + static_cast(IGEL_SQUARE_NB) * static_cast(PS_END); // Maximum number of simultaneously active features static constexpr IndexType kMaxActiveDimensions = PIECE_ID_KING; // Trigger for full calculation instead of difference calculation diff --git a/engines/igel/nnue/features/index_list.h b/engines/igel/igelnnue/features/index_list.h similarity index 100% rename from engines/igel/nnue/features/index_list.h rename to engines/igel/igelnnue/features/index_list.h diff --git a/engines/igel/nnue/layers/affine_transform.h b/engines/igel/igelnnue/layers/affine_transform.h similarity index 100% rename from engines/igel/nnue/layers/affine_transform.h rename to engines/igel/igelnnue/layers/affine_transform.h diff --git a/engines/igel/nnue/layers/clipped_relu.h b/engines/igel/igelnnue/layers/clipped_relu.h similarity index 100% rename from engines/igel/nnue/layers/clipped_relu.h rename to engines/igel/igelnnue/layers/clipped_relu.h diff --git a/engines/igel/nnue/layers/input_slice.h b/engines/igel/igelnnue/layers/input_slice.h similarity index 100% rename from engines/igel/nnue/layers/input_slice.h rename to engines/igel/igelnnue/layers/input_slice.h diff --git a/engines/igel/nnue/nnue_accumulator.h b/engines/igel/igelnnue/nnue_accumulator.h similarity index 100% rename from engines/igel/nnue/nnue_accumulator.h rename to engines/igel/igelnnue/nnue_accumulator.h diff --git a/engines/igel/nnue/nnue_architecture.h b/engines/igel/igelnnue/nnue_architecture.h similarity index 100% rename from engines/igel/nnue/nnue_architecture.h rename to engines/igel/igelnnue/nnue_architecture.h diff --git a/engines/igel/nnue/nnue_common.h b/engines/igel/igelnnue/nnue_common.h similarity index 100% rename from engines/igel/nnue/nnue_common.h rename to engines/igel/igelnnue/nnue_common.h diff --git a/engines/igel/nnue/nnue_feature_transformer.h b/engines/igel/igelnnue/nnue_feature_transformer.h similarity index 100% rename from engines/igel/nnue/nnue_feature_transformer.h rename to engines/igel/igelnnue/nnue_feature_transformer.h diff --git a/engines/igel/igeltypes.h b/engines/igel/igeltypes.h index 18b4b5d..21fb6b4 100644 --- a/engines/igel/igeltypes.h +++ b/engines/igel/igeltypes.h @@ -158,26 +158,26 @@ struct Pair inline Pair operator*(int x, const Pair& p) { return Pair(x * p.mid, x * p.end); } -#define SQUARE_ZERO 0 -#define SQUARE_NB 64 +//#define SQUARE_ZERO 0 +#define IGEL_SQUARE_NB 64 // unique number for each piece type on each square enum PieceSquare : uint32_t { PS_NONE = 0, PS_W_PAWN = 1, - PS_B_PAWN = 1 * SQUARE_NB + 1, - PS_W_KNIGHT = 2 * SQUARE_NB + 1, - PS_B_KNIGHT = 3 * SQUARE_NB + 1, - PS_W_BISHOP = 4 * SQUARE_NB + 1, - PS_B_BISHOP = 5 * SQUARE_NB + 1, - PS_W_ROOK = 6 * SQUARE_NB + 1, - PS_B_ROOK = 7 * SQUARE_NB + 1, - PS_W_QUEEN = 8 * SQUARE_NB + 1, - PS_B_QUEEN = 9 * SQUARE_NB + 1, - PS_W_KING = 10 * SQUARE_NB + 1, + PS_B_PAWN = 1 * IGEL_SQUARE_NB + 1, + PS_W_KNIGHT = 2 * IGEL_SQUARE_NB + 1, + PS_B_KNIGHT = 3 * IGEL_SQUARE_NB + 1, + PS_W_BISHOP = 4 * IGEL_SQUARE_NB + 1, + PS_B_BISHOP = 5 * IGEL_SQUARE_NB + 1, + PS_W_ROOK = 6 * IGEL_SQUARE_NB + 1, + PS_B_ROOK = 7 * IGEL_SQUARE_NB + 1, + PS_W_QUEEN = 8 * IGEL_SQUARE_NB + 1, + PS_B_QUEEN = 9 * IGEL_SQUARE_NB + 1, + PS_W_KING = 10 * IGEL_SQUARE_NB + 1, PS_END = PS_W_KING, // pieces without kings (pawns included) - PS_B_KING = 11 * SQUARE_NB + 1, - PS_END2 = 12 * SQUARE_NB + 1 + PS_B_KING = 11 * IGEL_SQUARE_NB + 1, + PS_END2 = 12 * IGEL_SQUARE_NB + 1 }; // An ID used to track the pieces. Max. 32 pieces on board. @@ -219,10 +219,10 @@ enum Value : int { MidgameLimit = 15258, EndgameLimit = 3915 }; -#define COLOR_NB 2 +#define IGEL_COLOR_NB 2 struct ExtPieceSquare { - PieceSquare from[COLOR_NB]; + PieceSquare from[IGEL_COLOR_NB]; }; enum Piece { @@ -261,7 +261,7 @@ class EvalList { static const int MAX_LENGTH = 32; // Array that holds the piece id for the pieces on the board - PieceId piece_id_list[SQUARE_NB]; + PieceId piece_id_list[IGEL_SQUARE_NB]; // List of pieces, separate from White and Black POV PieceSquare* piece_list_fw() const { return const_cast(pieceListFw); } diff --git a/engines/stockfish/.depend b/engines/stockfish/.depend new file mode 100644 index 0000000..d352983 --- /dev/null +++ b/engines/stockfish/.depend @@ -0,0 +1,171 @@ +benchmark.o: benchmark.cpp position.h bitboard.h types.h tune.h \ + evaluate.h psqt.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/nnue_common.h nnue/../misc.h nnue/features/half_ka_v2.h \ + nnue/features/../nnue_common.h nnue/features/../../evaluate.h \ + nnue/features/../../misc.h nnue/layers/input_slice.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform.h \ + nnue/layers/clipped_relu.h +bitbase.o: bitbase.cpp bitboard.h types.h tune.h +bitboard.o: bitboard.cpp bitboard.h types.h tune.h misc.h +endgame.o: endgame.cpp bitboard.h types.h tune.h endgame.h position.h \ + evaluate.h psqt.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/nnue_common.h nnue/../misc.h nnue/features/half_ka_v2.h \ + nnue/features/../nnue_common.h nnue/features/../../evaluate.h \ + nnue/features/../../misc.h nnue/layers/input_slice.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform.h \ + nnue/layers/clipped_relu.h movegen.h +evaluate.o: evaluate.cpp bitboard.h types.h tune.h evaluate.h material.h \ + endgame.h position.h psqt.h nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/nnue_common.h nnue/../misc.h \ + nnue/features/half_ka_v2.h nnue/features/../nnue_common.h \ + nnue/features/../../evaluate.h nnue/features/../../misc.h \ + nnue/layers/input_slice.h nnue/layers/../nnue_common.h \ + nnue/layers/affine_transform.h nnue/layers/clipped_relu.h misc.h \ + pawns.h thread.h movepick.h movegen.h search.h thread_win32_osx.h \ + timeman.h uci.h incbin/incbin.h +main.o: main.cpp bitboard.h types.h tune.h endgame.h position.h \ + evaluate.h psqt.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/nnue_common.h nnue/../misc.h nnue/features/half_ka_v2.h \ + nnue/features/../nnue_common.h nnue/features/../../evaluate.h \ + nnue/features/../../misc.h nnue/layers/input_slice.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform.h \ + nnue/layers/clipped_relu.h search.h misc.h movepick.h movegen.h \ + syzygy/tbprobe.h syzygy/../search.h thread.h material.h pawns.h \ + thread_win32_osx.h tt.h uci.h +material.o: material.cpp material.h endgame.h position.h bitboard.h \ + types.h tune.h evaluate.h psqt.h nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/nnue_common.h nnue/../misc.h \ + nnue/features/half_ka_v2.h nnue/features/../nnue_common.h \ + nnue/features/../../evaluate.h nnue/features/../../misc.h \ + nnue/layers/input_slice.h nnue/layers/../nnue_common.h \ + nnue/layers/affine_transform.h nnue/layers/clipped_relu.h misc.h \ + thread.h movepick.h movegen.h pawns.h search.h thread_win32_osx.h +misc.o: misc.cpp misc.h types.h tune.h thread.h material.h endgame.h \ + position.h bitboard.h evaluate.h psqt.h nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/nnue_common.h nnue/../misc.h \ + nnue/features/half_ka_v2.h nnue/features/../nnue_common.h \ + nnue/features/../../evaluate.h nnue/features/../../misc.h \ + nnue/layers/input_slice.h nnue/layers/../nnue_common.h \ + nnue/layers/affine_transform.h nnue/layers/clipped_relu.h movepick.h \ + movegen.h pawns.h search.h thread_win32_osx.h +movegen.o: movegen.cpp movegen.h types.h tune.h position.h bitboard.h \ + evaluate.h psqt.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/nnue_common.h nnue/../misc.h nnue/features/half_ka_v2.h \ + nnue/features/../nnue_common.h nnue/features/../../evaluate.h \ + nnue/features/../../misc.h nnue/layers/input_slice.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform.h \ + nnue/layers/clipped_relu.h +movepick.o: movepick.cpp movepick.h movegen.h types.h tune.h position.h \ + bitboard.h evaluate.h psqt.h nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/nnue_common.h nnue/../misc.h \ + nnue/features/half_ka_v2.h nnue/features/../nnue_common.h \ + nnue/features/../../evaluate.h nnue/features/../../misc.h \ + nnue/layers/input_slice.h nnue/layers/../nnue_common.h \ + nnue/layers/affine_transform.h nnue/layers/clipped_relu.h +pawns.o: pawns.cpp bitboard.h types.h tune.h pawns.h misc.h position.h \ + evaluate.h psqt.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/nnue_common.h nnue/../misc.h nnue/features/half_ka_v2.h \ + nnue/features/../nnue_common.h nnue/features/../../evaluate.h \ + nnue/features/../../misc.h nnue/layers/input_slice.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform.h \ + nnue/layers/clipped_relu.h thread.h material.h endgame.h movepick.h \ + movegen.h search.h thread_win32_osx.h +position.o: position.cpp bitboard.h types.h tune.h misc.h movegen.h \ + position.h evaluate.h psqt.h nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/nnue_common.h nnue/../misc.h \ + nnue/features/half_ka_v2.h nnue/features/../nnue_common.h \ + nnue/features/../../evaluate.h nnue/features/../../misc.h \ + nnue/layers/input_slice.h nnue/layers/../nnue_common.h \ + nnue/layers/affine_transform.h nnue/layers/clipped_relu.h thread.h \ + material.h endgame.h movepick.h pawns.h search.h thread_win32_osx.h \ + tt.h uci.h syzygy/tbprobe.h syzygy/../search.h +psqt.o: psqt.cpp psqt.h types.h tune.h bitboard.h +search.o: search.cpp evaluate.h types.h tune.h misc.h movegen.h \ + movepick.h position.h bitboard.h psqt.h nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/nnue_common.h nnue/../misc.h \ + nnue/features/half_ka_v2.h nnue/features/../nnue_common.h \ + nnue/features/../../evaluate.h nnue/features/../../misc.h \ + nnue/layers/input_slice.h nnue/layers/../nnue_common.h \ + nnue/layers/affine_transform.h nnue/layers/clipped_relu.h search.h \ + thread.h material.h endgame.h pawns.h thread_win32_osx.h timeman.h \ + tt.h uci.h syzygy/tbprobe.h syzygy/../search.h +thread.o: thread.cpp movegen.h types.h tune.h search.h misc.h movepick.h \ + position.h bitboard.h evaluate.h psqt.h nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/nnue_common.h nnue/../misc.h \ + nnue/features/half_ka_v2.h nnue/features/../nnue_common.h \ + nnue/features/../../evaluate.h nnue/features/../../misc.h \ + nnue/layers/input_slice.h nnue/layers/../nnue_common.h \ + nnue/layers/affine_transform.h nnue/layers/clipped_relu.h thread.h \ + material.h endgame.h pawns.h thread_win32_osx.h uci.h syzygy/tbprobe.h \ + syzygy/../search.h tt.h +timeman.o: timeman.cpp search.h misc.h types.h tune.h movepick.h \ + movegen.h position.h bitboard.h evaluate.h psqt.h \ + nnue/nnue_accumulator.h nnue/nnue_architecture.h nnue/nnue_common.h \ + nnue/../misc.h nnue/features/half_ka_v2.h \ + nnue/features/../nnue_common.h nnue/features/../../evaluate.h \ + nnue/features/../../misc.h nnue/layers/input_slice.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform.h \ + nnue/layers/clipped_relu.h timeman.h thread.h material.h endgame.h \ + pawns.h thread_win32_osx.h uci.h +tt.o: tt.cpp bitboard.h types.h tune.h misc.h thread.h material.h \ + endgame.h position.h evaluate.h psqt.h nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/nnue_common.h nnue/../misc.h \ + nnue/features/half_ka_v2.h nnue/features/../nnue_common.h \ + nnue/features/../../evaluate.h nnue/features/../../misc.h \ + nnue/layers/input_slice.h nnue/layers/../nnue_common.h \ + nnue/layers/affine_transform.h nnue/layers/clipped_relu.h movepick.h \ + movegen.h pawns.h search.h thread_win32_osx.h tt.h uci.h +uci.o: uci.cpp evaluate.h types.h tune.h movegen.h position.h bitboard.h \ + psqt.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/nnue_common.h nnue/../misc.h nnue/features/half_ka_v2.h \ + nnue/features/../nnue_common.h nnue/features/../../evaluate.h \ + nnue/features/../../misc.h nnue/layers/input_slice.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform.h \ + nnue/layers/clipped_relu.h search.h misc.h movepick.h thread.h \ + material.h endgame.h pawns.h thread_win32_osx.h timeman.h tt.h uci.h \ + syzygy/tbprobe.h syzygy/../search.h +ucioption.o: ucioption.cpp evaluate.h types.h tune.h misc.h search.h \ + movepick.h movegen.h position.h bitboard.h psqt.h \ + nnue/nnue_accumulator.h nnue/nnue_architecture.h nnue/nnue_common.h \ + nnue/../misc.h nnue/features/half_ka_v2.h \ + nnue/features/../nnue_common.h nnue/features/../../evaluate.h \ + nnue/features/../../misc.h nnue/layers/input_slice.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform.h \ + nnue/layers/clipped_relu.h thread.h material.h endgame.h pawns.h \ + thread_win32_osx.h tt.h uci.h syzygy/tbprobe.h syzygy/../search.h +tune.o: tune.cpp types.h tune.h misc.h uci.h +tbprobe.o: syzygy/tbprobe.cpp syzygy/../bitboard.h syzygy/../types.h \ + syzygy/../tune.h syzygy/../movegen.h syzygy/../position.h \ + syzygy/../evaluate.h syzygy/../psqt.h \ + syzygy/../nnue/nnue_accumulator.h syzygy/../nnue/nnue_architecture.h \ + syzygy/../nnue/nnue_common.h syzygy/../nnue/../misc.h \ + syzygy/../nnue/features/half_ka_v2.h \ + syzygy/../nnue/features/../nnue_common.h \ + syzygy/../nnue/features/../../evaluate.h \ + syzygy/../nnue/features/../../misc.h \ + syzygy/../nnue/layers/input_slice.h \ + syzygy/../nnue/layers/../nnue_common.h \ + syzygy/../nnue/layers/affine_transform.h \ + syzygy/../nnue/layers/clipped_relu.h syzygy/../search.h \ + syzygy/../misc.h syzygy/../movepick.h syzygy/../uci.h syzygy/tbprobe.h +evaluate_nnue.o: nnue/evaluate_nnue.cpp nnue/../evaluate.h \ + nnue/../types.h nnue/../tune.h nnue/../position.h nnue/../bitboard.h \ + nnue/../psqt.h nnue/../nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/nnue_common.h nnue/../misc.h \ + nnue/features/half_ka_v2.h nnue/features/../nnue_common.h \ + nnue/features/../../evaluate.h nnue/features/../../misc.h \ + nnue/layers/input_slice.h nnue/layers/../nnue_common.h \ + nnue/layers/affine_transform.h nnue/layers/clipped_relu.h \ + nnue/../uci.h nnue/evaluate_nnue.h nnue/nnue_feature_transformer.h +half_ka_v2.o: nnue/features/half_ka_v2.cpp nnue/features/half_ka_v2.h \ + nnue/features/../nnue_common.h nnue/features/../../misc.h \ + nnue/features/../../types.h nnue/features/../../tune.h \ + nnue/features/../../evaluate.h nnue/features/../../position.h \ + nnue/features/../../bitboard.h nnue/features/../../psqt.h \ + nnue/features/../../nnue/nnue_accumulator.h \ + nnue/features/../nnue_architecture.h \ + nnue/features/../features/half_ka_v2.h \ + nnue/features/../layers/input_slice.h \ + nnue/features/../layers/../nnue_common.h \ + nnue/features/../layers/affine_transform.h \ + nnue/features/../layers/clipped_relu.h diff --git a/engines/stockfish/Makefile b/engines/stockfish/Makefile index 54868b3..1d972cf 100644 --- a/engines/stockfish/Makefile +++ b/engines/stockfish/Makefile @@ -1,7 +1,5 @@ # Stockfish, a UCI chess playing engine derived from Glaurung 2.1 -# Copyright (C) 2004-2008 Tord Romstad (Glaurung author) -# Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad -# Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad +# Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) # # Stockfish is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -33,13 +31,17 @@ PREFIX = /usr/local BINDIR = $(PREFIX)/bin ### Built-in benchmark for pgo-builds -PGOBENCH = ./$(EXE) bench +ifeq ($(SDE_PATH),) + PGOBENCH = ./$(EXE) bench +else + PGOBENCH = $(SDE_PATH) -- ./$(EXE) bench +endif ### Source and object files SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \ material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp + nnue/evaluate_nnue.cpp nnue/features/half_ka_v2.cpp OBJS = $(notdir $(SRCS:.cpp=.o)) @@ -59,9 +61,11 @@ endif # ---------------------------------------------------------------------------- # # debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode -# sanitize = undefined/thread/no (-fsanitize ) +# sanitize = none/ ... (-fsanitize ) # --- ( undefined ) --- enable undefined behavior checks -# --- ( thread ) --- enable threading error checks +# --- ( thread ) --- enable threading error checks +# --- ( address ) --- enable memory access checks +# --- ...etc... --- see compiler documentation for supported sanitizers # optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations # arch = (name) --- (-arch) --- Target architecture # bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system @@ -82,6 +86,10 @@ endif # Note that Makefile is space sensitive, so when adding new architectures # or modifying existing flags, you have to make sure there are no extra spaces # at the end of the line for flag values. +# +# Example of use for these flags: +# make build ARCH=x86-64-avx512 debug=on sanitize="address undefined" + ### 2.1. General and architecture defaults @@ -94,7 +102,7 @@ endif ifeq ($(ARCH), $(filter $(ARCH), \ x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-bmi2 x86-64-avx2 \ x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ - x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 \ + x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \ armv7 armv7-neon armv8 apple-silicon general-64 general-32)) SUPPORTED_ARCH=true else @@ -103,7 +111,7 @@ endif optimize = yes debug = no -sanitize = no +sanitize = none bits = 64 prefetch = no popcnt = no @@ -289,6 +297,17 @@ ifeq ($(ARCH),ppc-64) prefetch = yes endif +ifeq ($(findstring e2k,$(ARCH)),e2k) + arch = e2k + mmx = yes + bits = 64 + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + popcnt = yes +endif + endif ### ========================================================================== @@ -366,9 +385,13 @@ ifeq ($(COMP),clang) ifneq ($(KERNEL),Darwin) ifneq ($(KERNEL),OpenBSD) + ifneq ($(KERNEL),FreeBSD) + ifneq ($(RTLIB),compiler-rt) LDFLAGS += -latomic endif endif + endif + endif ifeq ($(arch),$(filter $(arch),armv7 armv8)) ifeq ($(OS),Android) @@ -458,9 +481,9 @@ else endif ### 3.2.2 Debugging with undefined behavior sanitizers -ifneq ($(sanitize),no) - CXXFLAGS += -g3 -fsanitize=$(sanitize) - LDFLAGS += -fsanitize=$(sanitize) +ifneq ($(sanitize),none) + CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize)) + LDFLAGS += $(addprefix -fsanitize=,$(sanitize)) endif ### 3.3 Optimization @@ -479,6 +502,10 @@ ifeq ($(optimize),yes) CXXFLAGS += -mdynamic-no-pic endif endif + + ifeq ($(comp),clang) + CXXFLAGS += -fexperimental-new-pass-manager + endif endif ### 3.4 Bits @@ -506,7 +533,6 @@ ifeq ($(popcnt),yes) endif endif - ifeq ($(avx2),yes) CXXFLAGS += -DUSE_AVX2 ifeq ($(comp),$(filter $(comp),gcc clang mingw)) @@ -588,7 +614,7 @@ endif ifeq ($(optimize),yes) ifeq ($(debug), no) ifeq ($(comp),clang) - CXXFLAGS += -flto=thin + CXXFLAGS += -flto ifneq ($(findstring MINGW,$(KERNEL)),) CXXFLAGS += -fuse-ld=lld else ifneq ($(findstring MSYS,$(KERNEL)),) @@ -608,7 +634,7 @@ ifeq ($(debug), no) LDFLAGS += -save-temps endif else - CXXFLAGS += -flto=thin + CXXFLAGS += -flto LDFLAGS += $(CXXFLAGS) endif @@ -674,6 +700,7 @@ help: @echo "armv7 > ARMv7 32-bit" @echo "armv7-neon > ARMv7 32-bit with popcnt and neon" @echo "armv8 > ARMv8 64-bit with popcnt and neon" + @echo "e2k > Elbrus 2000" @echo "apple-silicon > Apple silicon ARM64" @echo "general-64 > unspecified 64-bit" @echo "general-32 > unspecified 32-bit" @@ -711,7 +738,7 @@ endif config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \ clang-profile-use clang-profile-make -build: config-sanity net +build: net config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all profile-build: net config-sanity objclean profileclean @@ -774,6 +801,9 @@ profileclean: @rm -rf profdir @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s @rm -f stockfish.profdata *.profraw + @rm -f stockfish.exe.lto_wrapper_args + @rm -f stockfish.exe.ltrans.out + @rm -f ./-lstdc++.res default: help @@ -784,7 +814,7 @@ default: all: $(EXE) .depend -config-sanity: +config-sanity: net @echo "" @echo "Config:" @echo "debug: '$(debug)'" @@ -816,11 +846,10 @@ config-sanity: @echo "Testing config sanity. If this fails, try 'make help' ..." @echo "" @test "$(debug)" = "yes" || test "$(debug)" = "no" - @test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no" @test "$(optimize)" = "yes" || test "$(optimize)" = "no" @test "$(SUPPORTED_ARCH)" = "true" @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ - test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || \ + test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \ test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" @test "$(bits)" = "32" || test "$(bits)" = "64" @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" @@ -856,14 +885,15 @@ clang-profile-use: all gcc-profile-make: + @mkdir -p profdir $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ - EXTRACXXFLAGS='-fprofile-generate' \ + EXTRACXXFLAGS='-fprofile-generate=profdir' \ EXTRALDFLAGS='-lgcov' \ all gcc-profile-use: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ - EXTRACXXFLAGS='-fprofile-use -fno-peel-loops -fno-tracer' \ + EXTRACXXFLAGS='-fprofile-use=profdir -fno-peel-loops -fno-tracer' \ EXTRALDFLAGS='-lgcov' \ all diff --git a/engines/stockfish/benchmark.cpp b/engines/stockfish/benchmark.cpp index 5a713a2..7945a45 100644 --- a/engines/stockfish/benchmark.cpp +++ b/engines/stockfish/benchmark.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -92,6 +92,8 @@ const vector Defaults = { } // namespace +namespace Stockfish { + /// setup_bench() builds a list of UCI commands to be run by bench. There /// are five parameters: TT size in MB, number of search threads that /// should be used, the limit value spent for each position, a file name @@ -156,15 +158,17 @@ vector setup_bench(const Position& current, istream& is) { else { if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0)) - list.emplace_back("setoption name Use NNUE value 0"); + list.emplace_back("setoption name Use NNUE value false"); else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0)) - list.emplace_back("setoption name Use NNUE value 1"); + list.emplace_back("setoption name Use NNUE value true"); list.emplace_back("position fen " + fen); list.emplace_back(go); ++posCounter; } - list.emplace_back("setoption name Use NNUE value 1"); + list.emplace_back("setoption name Use NNUE value true"); return list; } + +} // namespace Stockfish diff --git a/engines/stockfish/bitbase.cpp b/engines/stockfish/bitbase.cpp index bbe8e9a..27bf409 100644 --- a/engines/stockfish/bitbase.cpp +++ b/engines/stockfish/bitbase.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ #include "bitboard.h" #include "types.h" +namespace Stockfish { + namespace { // There are 24 possible pawn squares: files A to D and ranks from 2 to 7. @@ -66,7 +68,6 @@ namespace { } // namespace - bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) { assert(file_of(wpsq) <= FILE_D); @@ -96,7 +97,6 @@ void Bitbases::init() { KPKBitbase.set(idx); } - namespace { KPKPosition::KPKPosition(unsigned idx) { @@ -150,8 +150,8 @@ namespace { Bitboard b = attacks_bb(ksq[stm]); while (b) - r |= stm == WHITE ? db[index(BLACK, ksq[BLACK] , pop_lsb(&b), psq)] - : db[index(WHITE, pop_lsb(&b), ksq[WHITE], psq)]; + r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)] + : db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)]; if (stm == WHITE) { @@ -168,3 +168,5 @@ namespace { } } // namespace + +} // namespace Stockfish diff --git a/engines/stockfish/bitboard.cpp b/engines/stockfish/bitboard.cpp index 80206b5..6b84b51 100644 --- a/engines/stockfish/bitboard.cpp +++ b/engines/stockfish/bitboard.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,11 +22,14 @@ #include "bitboard.h" #include "misc.h" +namespace Stockfish { + uint8_t PopCnt16[1 << 16]; uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; Bitboard SquareBB[SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; @@ -42,7 +45,6 @@ namespace { } - /// safe_destination() returns the bitboard of target square for the given step /// from the given square. If the step is off the board, returns empty bitboard. @@ -55,7 +57,7 @@ inline Bitboard safe_destination(Square s, int step) { /// Bitboards::pretty() returns an ASCII representation of a bitboard suitable /// to be printed to standard output. Useful for debugging. -const std::string Bitboards::pretty(Bitboard b) { +std::string Bitboards::pretty(Bitboard b) { std::string s = "+---+---+---+---+---+---+---+---+\n"; @@ -106,12 +108,17 @@ void Bitboards::init() { for (PieceType pt : { BISHOP, ROOK }) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + { if (PseudoAttacks[pt][s1] & s2) - LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + { + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); + } + BetweenBB[s1][s2] |= s2; + } } } - namespace { Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { @@ -123,7 +130,7 @@ namespace { for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) { Square s = sq; - while(safe_destination(s, d) && !(occupied & s)) + while (safe_destination(s, d) && !(occupied & s)) attacks |= (s += d); } @@ -211,3 +218,5 @@ namespace { } } } + +} // namespace Stockfish diff --git a/engines/stockfish/bitboard.h b/engines/stockfish/bitboard.h index 29d8f66..b29f3e2 100644 --- a/engines/stockfish/bitboard.h +++ b/engines/stockfish/bitboard.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,19 +23,21 @@ #include "types.h" +namespace Stockfish { + namespace Bitbases { void init(); bool probe(Square wksq, Square wpsq, Square bksq, Color us); -} +} // namespace Stockfish::Bitbases namespace Bitboards { void init(); -const std::string pretty(Bitboard b); +std::string pretty(Bitboard b); -} +} // namespace Stockfish::Bitboards constexpr Bitboard AllSquares = ~Bitboard(0); constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; @@ -73,6 +75,7 @@ extern uint8_t PopCnt16[1 << 16]; extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; extern Bitboard SquareBB[SQUARE_NB]; +extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; @@ -209,23 +212,29 @@ constexpr Bitboard adjacent_files_bb(Square s) { inline Bitboard line_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); + return LineBB[s1][s2]; } -/// between_bb() returns a bitboard representing squares that are linearly -/// between the two given squares (excluding the given squares). If the given -/// squares are not on a same file/rank/diagonal, we return 0. For instance, -/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5 and E6. +/// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open +/// segment between the squares s1 and s2 (excluding s1 but including s2). If the +/// given squares are not on a same file/rank/diagonal, it returns s2. For instance, +/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but +/// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick +/// allows to generate non-king evasion moves faster: the defending piece must either +/// interpose itself to cover the check or capture the checking piece. inline Bitboard between_bb(Square s1, Square s2) { - Bitboard b = line_bb(s1, s2) & ((AllSquares << s1) ^ (AllSquares << s2)); - return b & (b - 1); //exclude lsb + + assert(is_ok(s1) && is_ok(s2)); + + return BetweenBB[s1][s2]; } -/// forward_ranks_bb() returns a bitboard representing the squares on the ranks -/// in front of the given one, from the point of view of the given color. For instance, +/// forward_ranks_bb() returns a bitboard representing the squares on the ranks in +/// front of the given one, from the point of view of the given color. For instance, /// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. constexpr Bitboard forward_ranks_bb(Color c, Square s) { @@ -412,13 +421,20 @@ inline Square msb(Bitboard b) { #endif +/// least_significant_square_bb() returns the bitboard of the least significant +/// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). + +inline Bitboard least_significant_square_bb(Bitboard b) { + assert(b); + return b & -b; +} /// pop_lsb() finds and clears the least significant bit in a non-zero bitboard -inline Square pop_lsb(Bitboard* b) { - assert(*b); - const Square s = lsb(*b); - *b &= *b - 1; +inline Square pop_lsb(Bitboard& b) { + assert(b); + const Square s = lsb(b); + b &= b - 1; return s; } @@ -430,4 +446,6 @@ inline Square frontmost_sq(Color c, Bitboard b) { return c == WHITE ? msb(b) : lsb(b); } +} // namespace Stockfish + #endif // #ifndef BITBOARD_H_INCLUDED diff --git a/engines/stockfish/endgame.cpp b/engines/stockfish/endgame.cpp index c8be219..a44d3a1 100644 --- a/engines/stockfish/endgame.cpp +++ b/engines/stockfish/endgame.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,6 +22,8 @@ #include "endgame.h" #include "movegen.h" +namespace Stockfish { + namespace { // Used to drive the king towards the edge of the board @@ -553,8 +555,8 @@ ScaleFactor Endgame::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, RookValueMg, 2)); assert(verify_material(pos, weakSide, RookValueMg, 1)); - Square strongPawn1 = pos.squares(strongSide)[0]; - Square strongPawn2 = pos.squares(strongSide)[1]; + Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); + Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); Square weakKing = pos.square(weakSide); // Does the stronger side have a passed pawn? @@ -638,8 +640,8 @@ ScaleFactor Endgame::operator()(const Position& pos) const { return SCALE_FACTOR_NONE; Square weakKing = pos.square(weakSide); - Square strongPawn1 = pos.squares(strongSide)[0]; - Square strongPawn2 = pos.squares(strongSide)[1]; + Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); + Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); Square blockSq1, blockSq2; if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2)) @@ -741,3 +743,5 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // it's probably at least a draw even with the pawn. return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; } + +} // namespace Stockfish diff --git a/engines/stockfish/endgame.h b/engines/stockfish/endgame.h index 1351d88..146111b 100644 --- a/engines/stockfish/endgame.h +++ b/engines/stockfish/endgame.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,6 +28,7 @@ #include "position.h" #include "types.h" +namespace Stockfish { /// EndgameCode lists all supported endgame functions by corresponding codes @@ -120,4 +121,6 @@ namespace Endgames { } } +} // namespace Stockfish + #endif // #ifndef ENDGAME_H_INCLUDED diff --git a/engines/stockfish/evaluate.cpp b/engines/stockfish/evaluate.cpp index 97fc70c..538214d 100644 --- a/engines/stockfish/evaluate.cpp +++ b/engines/stockfish/evaluate.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,16 +33,18 @@ #include "misc.h" #include "pawns.h" #include "thread.h" +#include "timeman.h" #include "uci.h" #include "incbin/incbin.h" -// Macro to embed the default NNUE file data in the engine binary (using incbin.h, by Dale Weiler). +// Macro to embed the default efficiently updatable neural network (NNUE) file +// data in the engine binary (using incbin.h, by Dale Weiler). // This macro invocation will declare the following three variables // const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data // const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end // const unsigned int gEmbeddedNNUESize; // the size of the embedded file -// Note that this does not work in Microsof Visual Studio. +// Note that this does not work in Microsoft Visual Studio. #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) INCBIN(EmbeddedNNUE, EvalFileDefaultName); #else @@ -53,25 +55,23 @@ using namespace std; -using namespace Eval::NNUE; -namespace Eval { +namespace Stockfish { - int useNNUE; - int64_t nnueHits; - int64_t evalHits; +namespace Eval { + bool useNNUE; string eval_file_loaded = "None"; - /// init_NNUE() tries to load a nnue network at startup time, or when the engine + /// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" - /// The name of the nnue network is always retrieved from the EvalFile option. + /// The name of the NNUE network is always retrieved from the EvalFile option. /// We search the given network in three locations: internally (the default /// network may be embedded in the binary), in the active working directory and /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY /// variable to have the engine search in a special directory in their distro. - void init_NNUE() { + void NNUE::init() { useNNUE = Options["Use NNUE"]; if (!useNNUE) @@ -114,8 +114,8 @@ namespace Eval { } } - /// verify_NNUE() verifies that the last net used was loaded successfully - void verify_NNUE() { + /// NNUE::verify() verifies that the last net used was loaded successfully + void NNUE::verify() { string eval_file = string(Options["EvalFile"]); @@ -180,7 +180,7 @@ namespace Trace { else os << scores[t][WHITE] << " | " << scores[t][BLACK]; - os << " | " << scores[t][WHITE] - scores[t][BLACK] << "\n"; + os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n"; return os; } } @@ -190,11 +190,9 @@ using namespace Trace; namespace { // Threshold for lazy and space evaluation - constexpr Value LazyThreshold1 = Value(1400); - constexpr Value LazyThreshold2 = Value(1300); - constexpr Value SpaceThreshold = Value(12222); - constexpr Value NNUEThreshold1 = Value(550); - constexpr Value NNUEThreshold2 = Value(150); + constexpr Value LazyThreshold1 = Value(3130); + constexpr Value LazyThreshold2 = Value(2204); + constexpr Value SpaceThreshold = Value(11551); // KingAttackWeights[PieceType] contains king attack weights by piece type constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 }; @@ -202,7 +200,7 @@ namespace { // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type, // higher if multiple safe checks are possible for that piece type. constexpr int SafeCheck[][2] = { - {}, {}, {792, 1283}, {645, 967}, {1084, 1897}, {772, 1119} + {}, {}, {803, 1292}, {639, 974}, {1087, 1878}, {759, 1132} }; #define S(mg, eg) make_score(mg, eg) @@ -210,19 +208,25 @@ namespace { // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game, // indexed by piece type and number of attacked squares in the mobility area. constexpr Score MobilityBonus[][32] = { - { S(-62,-81), S(-53,-56), S(-12,-31), S( -4,-16), S( 3, 5), S( 13, 11), // Knight - S( 22, 17), S( 28, 20), S( 33, 25) }, - { S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishop - S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86), - S( 91, 88), S( 98, 97) }, - { S(-60,-78), S(-20,-17), S( 2, 23), S( 3, 39), S( 3, 70), S( 11, 99), // Rook - S( 22,103), S( 31,121), S( 40,134), S( 40,139), S( 41,158), S( 48,164), - S( 57,168), S( 57,169), S( 62,172) }, - { S(-30,-48), S(-12,-30), S( -8, -7), S( -9, 19), S( 20, 40), S( 23, 55), // Queen - S( 23, 59), S( 35, 75), S( 38, 78), S( 53, 96), S( 64, 96), S( 65,100), - S( 65,121), S( 66,127), S( 67,131), S( 67,133), S( 72,136), S( 72,141), - S( 77,147), S( 79,150), S( 93,151), S(108,168), S(108,168), S(108,171), - S(110,182), S(114,182), S(114,192), S(116,219) } + { S(-62,-79), S(-53,-57), S(-12,-31), S( -3,-17), S( 3, 7), S( 12, 13), // Knight + S( 21, 16), S( 28, 21), S( 37, 26) }, + { S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop + S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87), + S( 91, 88), S( 96, 98) }, + { S(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook + S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160), + S( 57,165), S( 58,170), S( 67,175) }, + { S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen + S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101), + S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140), + S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171), + S(112,178), S(114,185), S(114,187), S(119,221) } + }; + + // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on + // squares of the same color as our bishop. + constexpr Score BishopPawns[int(FILE_NB) / 2] = { + S(3, 8), S(3, 9), S(2, 8), S(3, 8) }; // KingProtector[knight/bishop] contains penalty for each distance unit to own king @@ -230,16 +234,15 @@ namespace { // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a // pawn protected square on rank 4 to 6 which is also safe from a pawn attack. - constexpr Score Outpost[] = { S(56, 34), S(31, 23) }; + constexpr Score Outpost[] = { S(57, 38), S(31, 24) }; // PassedRank[Rank] contains a bonus according to the rank of a passed pawn constexpr Score PassedRank[RANK_NB] = { - S(0, 0), S(9, 28), S(15, 31), S(17, 39), S(64, 70), S(171, 177), S(277, 260) + S(0, 0), S(7, 27), S(16, 32), S(17, 40), S(64, 71), S(170, 174), S(278, 262) }; - // RookOnFile[semiopen/open] contains bonuses for each rook when there is - // no (friendly) pawn on the rook file. - constexpr Score RookOnFile[] = { S(19, 7), S(48, 27) }; + constexpr Score RookOnClosedFile = S(10, 5); + constexpr Score RookOnOpenFile[] = { S(19, 6), S(47, 26) }; // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to // which piece type attacks which one. Attacks on lesser pieces which are @@ -252,12 +255,12 @@ namespace { S(0, 0), S(3, 44), S(37, 68), S(42, 60), S(0, 39), S(58, 43) }; + constexpr Value CorneredBishop = Value(50); + // Assorted bonuses and penalties - constexpr Score BadOutpost = S( -7, 36); + constexpr Score UncontestedOutpost = S( 1, 10); constexpr Score BishopOnKingRing = S( 24, 0); - constexpr Score BishopPawns = S( 3, 7); constexpr Score BishopXRayPawns = S( 4, 5); - constexpr Score CorneredBishop = S( 50, 50); constexpr Score FlankAttacks = S( 8, 0); constexpr Score Hanging = S( 69, 36); constexpr Score KnightOnQueen = S( 16, 11); @@ -268,7 +271,6 @@ namespace { constexpr Score ReachableOutpost = S( 31, 22); constexpr Score RestrictedPiece = S( 7, 7); constexpr Score RookOnKingRing = S( 16, 0); - constexpr Score RookOnQueenFile = S( 6, 11); constexpr Score SliderOnQueen = S( 60, 18); constexpr Score ThreatByKing = S( 24, 89); constexpr Score ThreatByPawnPush = S( 48, 39); @@ -387,15 +389,16 @@ namespace { constexpr Direction Down = -pawn_push(Us); constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB : Rank5BB | Rank4BB | Rank3BB); - const Square* pl = pos.squares(Us); - + Bitboard b1 = pos.pieces(Us, Pt); Bitboard b, bb; Score score = SCORE_ZERO; attackedBy[Us][Pt] = 0; - for (Square s = *pl; s != SQ_NONE; s = *++pl) + while (b1) { + Square s = pop_lsb(b1); + // Find attacked squares, including x-ray attacks for bishops and rooks b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) @@ -422,13 +425,12 @@ namespace { score += BishopOnKingRing; int mob = popcount(b & mobilityArea[Us]); - mobility[Us] += MobilityBonus[Pt - 2][mob]; if (Pt == BISHOP || Pt == KNIGHT) { // Bonus if the piece is on an outpost square or can reach one - // Reduced bonus for knights (BadOutpost) if few relevant targets + // Bonus for knights (UncontestedOutpost) if few relevant targets bb = OutpostRanks & (attackedBy[Us][PAWN] | shift(pos.pieces(PAWN))) & ~pe->pawn_attacks_span(Them); Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN); @@ -437,7 +439,7 @@ namespace { && bb & s & ~CenterFiles // on a side outpost && !(b & targets) // no relevant attacks && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide)))) - score += BadOutpost; + score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide)); else if (bb & s) score += Outpost[Pt == BISHOP]; else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) @@ -450,14 +452,14 @@ namespace { // Penalty if the piece is far from the king score -= KingProtector[Pt == BISHOP] * distance(pos.square(Us), s); - if (Pt == BISHOP) + if constexpr (Pt == BISHOP) { // Penalty according to the number of our pawns on the same color square as the // bishop, bigger when the center files are blocked with pawns and smaller // when the bishop is outside the pawn chain. Bitboard blocked = pos.pieces(Us, PAWN) & shift(pos.pieces()); - score -= BishopPawns * pos.pawns_on_same_color_squares(Us, s) + score -= BishopPawns[edge_distance(file_of(s))] * pos.pawns_on_same_color_squares(Us, s) * (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles)); // Penalty for all enemy pawns x-rayed @@ -475,33 +477,40 @@ namespace { { Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); if (pos.piece_on(s + d) == make_piece(Us, PAWN)) - score -= !pos.empty(s + d + pawn_push(Us)) ? CorneredBishop * 4 - : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2 - : CorneredBishop; + score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop) + : 3 * make_score(CorneredBishop, CorneredBishop); } } } - if (Pt == ROOK) + if constexpr (Pt == ROOK) { - // Bonus for rook on the same file as a queen - if (file_bb(s) & pos.pieces(QUEEN)) - score += RookOnQueenFile; - - // Bonus for rook on an open or semi-open file + // Bonuses for rook on a (semi-)open or closed file if (pos.is_on_semiopen_file(Us, s)) - score += RookOnFile[pos.is_on_semiopen_file(Them, s)]; - - // Penalty when trapped by the king, even more if the king cannot castle - else if (mob <= 3) { - File kf = file_of(pos.square(Us)); - if ((kf < FILE_E) == (file_of(s) < kf)) - score -= TrappedRook * (1 + !pos.castling_rights(Us)); + score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)]; + } + else + { + // If our pawn on this file is blocked, increase penalty + if ( pos.pieces(Us, PAWN) + & shift(pos.pieces()) + & file_bb(s)) + { + score -= RookOnClosedFile; + } + + // Penalty when trapped by the king, even more if the king cannot castle + if (mob <= 3) + { + File kf = file_of(pos.square(Us)); + if ((kf < FILE_E) == (file_of(s) < kf)) + score -= TrappedRook * (1 + !pos.castling_rights(Us)); + } } } - if (Pt == QUEEN) + if constexpr (Pt == QUEEN) { // Penalty if any relative pin or discovered attack against the queen Bitboard queenPinners; @@ -509,7 +518,7 @@ namespace { score -= WeakQueen; } } - if (T) + if constexpr (T) Trace::add(Pt, Us, score); return score; @@ -585,18 +594,18 @@ namespace { int kingFlankAttack = popcount(b1) + popcount(b2); int kingFlankDefense = popcount(b3); - kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] - + 185 * popcount(kingRing[Us] & weak) - + 148 * popcount(unsafeChecks) - + 98 * popcount(pos.blockers_for_king(Us)) - + 69 * kingAttacksCount[Them] - + 3 * kingFlankAttack * kingFlankAttack / 8 - + mg_value(mobility[Them] - mobility[Us]) - - 873 * !pos.count(Them) - - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) - - 6 * mg_value(score) / 8 - - 4 * kingFlankDefense - + 37; + kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] // (~10 Elo) + + 183 * popcount(kingRing[Us] & weak) // (~15 Elo) + + 148 * popcount(unsafeChecks) // (~4 Elo) + + 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo) + + 69 * kingAttacksCount[Them] // (~0.5 Elo) + + 3 * kingFlankAttack * kingFlankAttack / 8 // (~0.5 Elo) + + mg_value(mobility[Them] - mobility[Us]) // (~0.5 Elo) + - 873 * !pos.count(Them) // (~24 Elo) + - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) // (~5 Elo) + - 6 * mg_value(score) / 8 // (~8 Elo) + - 4 * kingFlankDefense // (~5 Elo) + + 37; // (~0.5 Elo) // Transform the kingDanger units into a Score, and subtract it from the evaluation if (kingDanger > 100) @@ -609,7 +618,7 @@ namespace { // Penalty if king flank is under attack, potentially moving toward the king score -= FlankAttacks * kingFlankAttack; - if (T) + if constexpr (T) Trace::add(KING, Us, score); return score; @@ -648,11 +657,11 @@ namespace { { b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); while (b) - score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(&b)))]; + score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(b)))]; b = weak & attackedBy[Us][ROOK]; while (b) - score += ThreatByRook[type_of(pos.piece_on(pop_lsb(&b)))]; + score += ThreatByRook[type_of(pos.piece_on(pop_lsb(b)))]; if (weak & attackedBy[Us][KING]) score += ThreatByKing; @@ -710,7 +719,7 @@ namespace { score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance); } - if (T) + if constexpr (T) Trace::add(THREAT, Us, score); return score; @@ -750,7 +759,7 @@ namespace { while (b) { - Square s = pop_lsb(&b); + Square s = pop_lsb(b); assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); @@ -780,14 +789,16 @@ namespace { bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN); if (!(pos.pieces(Them) & bb)) - unsafeSquares &= attackedBy[Them][ALL_PIECES]; + unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); - // If there are no enemy attacks on passed pawn span, assign a big bonus. + // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus. + // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus. // Otherwise assign a smaller bonus if the path to queen is not attacked // and even smaller bonus if it is attacked but block square is not. - int k = !unsafeSquares ? 35 : - !(unsafeSquares & squaresToQueen) ? 20 : - !(unsafeSquares & blockSq) ? 9 : + int k = !unsafeSquares ? 36 : + !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 : + !(unsafeSquares & squaresToQueen) ? 17 : + !(unsafeSquares & blockSq) ? 7 : 0 ; // Assign a larger bonus if the block square is defended @@ -801,7 +812,7 @@ namespace { score += bonus - PassedFile * edge_distance(file_of(s)); } - if (T) + if constexpr (T) Trace::add(PASSED, Us, score); return score; @@ -836,11 +847,13 @@ namespace { behind |= shift(behind); behind |= shift(behind); + // Compute space score based on the number of safe squares and number of our pieces + // increased with number of total blocked pawns in position. int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]); int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); Score score = make_score(bonus * weight * weight / 16, 0); - if (T) + if constexpr (T) Trace::add(SPACE, Us, score); return score; @@ -855,7 +868,7 @@ namespace { Value Evaluation::winnable(Score score) const { int outflanking = distance(pos.square(WHITE), pos.square(BLACK)) - - distance(pos.square(WHITE), pos.square(BLACK)); + + int(rank_of(pos.square(WHITE)) - rank_of(pos.square(BLACK))); bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) && (pos.pieces(PAWN) & KingSide); @@ -892,28 +905,42 @@ namespace { Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; int sf = me->scale_factor(pos, strongSide); - // If scale factor is not already specific, scale down via general heuristics + // If scale factor is not already specific, scale up/down via general heuristics if (sf == SCALE_FACTOR_NORMAL) { if (pos.opposite_bishops()) { + // For pure opposite colored bishops endgames use scale factor + // based on the number of passed pawns of the strong side. if ( pos.non_pawn_material(WHITE) == BishopValueMg && pos.non_pawn_material(BLACK) == BishopValueMg) sf = 18 + 4 * popcount(pe->passed_pawns(strongSide)); + // For every other opposite colored bishops endgames use scale factor + // based on the number of all pieces of the strong side. else sf = 22 + 3 * pos.count(strongSide); } + // For rook endgames with strong side not having overwhelming pawn number advantage + // and its pawns being on one flank and weak side protecting its pieces with a king + // use lower scale factor. else if ( pos.non_pawn_material(WHITE) == RookValueMg && pos.non_pawn_material(BLACK) == RookValueMg && pos.count(strongSide) - pos.count(~strongSide) <= 1 && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) sf = 36; + // For queen vs no queen endgames use scale factor + // based on number of minors of side that doesn't have queen. else if (pos.count() == 1) sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) : pos.count(WHITE) + pos.count(WHITE)); + // In every other case use scale factor based on + // the number of pawns of the strong side reduced if pawns are on a single flank. else - sf = std::min(sf, 36 + 7 * pos.count(strongSide)); + sf = std::min(sf, 36 + 7 * pos.count(strongSide)) - 4 * !pawnsOnBothFlanks; + + // Reduce scale factor in case of pawns being on a single flank + sf -= 4 * !pawnsOnBothFlanks; } // Interpolate between the middlegame and (scaled by 'sf') endgame score @@ -921,7 +948,7 @@ namespace { + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL; v /= PHASE_MIDGAME; - if (T) + if constexpr (T) { Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score))); Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL)); @@ -951,7 +978,7 @@ namespace { // Initialize score by reading the incrementally updated scores included in // the position object (material + piece square tables) and the material // imbalance. Score is computed internally from the white point of view. - Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->contempt; + Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->trend; // Probe the pawn hash table pe = Pawns::probe(pos); @@ -959,7 +986,7 @@ namespace { // Early exit if score is high auto lazy_skip = [&](Value lazyThreshold) { - return abs(mg_value(score) + eg_value(score)) / 2 > lazyThreshold + pos.non_pawn_material() / 64; + return abs(mg_value(score) + eg_value(score)) > lazyThreshold + pos.non_pawn_material() / 32; }; if (lazy_skip(LazyThreshold1)) @@ -993,7 +1020,7 @@ namespace { Value v = winnable(score); // In case of tracing add all remaining individual evaluation terms - if (T) + if constexpr (T) { Trace::add(MATERIAL, pos.psq_score()); Trace::add(IMBALANCE, me->imbalance()); @@ -1005,12 +1032,48 @@ namespace { v = (v / 16) * 16; // Side to move point of view - v = (pos.side_to_move() == WHITE ? v : -v) + Tempo; + v = (pos.side_to_move() == WHITE ? v : -v); return v; } -} // namespace + + /// Fisher Random Chess: correction for cornered bishops, to fix chess960 play with NNUE + + Value fix_FRC(const Position& pos) { + + constexpr Bitboard Corners = 1ULL << SQ_A1 | 1ULL << SQ_H1 | 1ULL << SQ_A8 | 1ULL << SQ_H8; + + if (!(pos.pieces(BISHOP) & Corners)) + return VALUE_ZERO; + + int correction = 0; + + if ( pos.piece_on(SQ_A1) == W_BISHOP + && pos.piece_on(SQ_B2) == W_PAWN) + correction += !pos.empty(SQ_B3) ? -CorneredBishop * 4 + : -CorneredBishop * 3; + + if ( pos.piece_on(SQ_H1) == W_BISHOP + && pos.piece_on(SQ_G2) == W_PAWN) + correction += !pos.empty(SQ_G3) ? -CorneredBishop * 4 + : -CorneredBishop * 3; + + if ( pos.piece_on(SQ_A8) == B_BISHOP + && pos.piece_on(SQ_B7) == B_PAWN) + correction += !pos.empty(SQ_B6) ? CorneredBishop * 4 + : CorneredBishop * 3; + + if ( pos.piece_on(SQ_H8) == B_BISHOP + && pos.piece_on(SQ_G7) == B_PAWN) + correction += !pos.empty(SQ_G6) ? CorneredBishop * 4 + : CorneredBishop * 3; + + return pos.side_to_move() == WHITE ? Value(correction) + : -Value(correction); + } + +} // namespace Eval /// evaluate() is the evaluator for the outer world. It returns a static @@ -1020,31 +1083,33 @@ Value Eval::evaluate(const Position& pos) { Value v; - evalHits++; if (!Eval::useNNUE) v = Evaluation(pos).value(); else { - // scale and shift NNUE for compatibility with search and classical evaluation - auto adjusted_NNUE = [&](){ nnueHits++; return NNUE::evaluate(pos) * 5 / 4 + Tempo; }; - - if (Eval::useNNUE == 2) { - v = adjusted_NNUE(); - } else { - // if there is PSQ imbalance use classical eval, with small probability if it is small - Value psq = Value(abs(eg_value(pos.psq_score()))); - int r50 = 16 + pos.rule50_count(); - bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50; - bool classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB)); + // Scale and shift NNUE for compatibility with search and classical evaluation + auto adjusted_NNUE = [&]() + { + int scale = 903 + + 32 * pos.count() + + 32 * pos.non_pawn_material() / 1024; - v = classical ? Evaluation(pos).value() : adjusted_NNUE(); + Value nnue = NNUE::evaluate(pos, true) * scale / 1024; - // if the classical eval is small and imbalance large, use NNUE nevertheless. - if ( largePsq - && abs(v) * 16 < NNUEThreshold2 * r50) - v = adjusted_NNUE(); - } + if (pos.is_chess960()) + nnue += fix_FRC(pos); + + return nnue; + }; + + // If there is PSQ imbalance we use the classical eval, but we switch to + // NNUE eval faster when shuffling or if the material on the board is high. + int r50 = pos.rule50_count(); + Value psq = Value(abs(eg_value(pos.psq_score()))); + bool classical = psq * 5 > (750 + pos.non_pawn_material() / 64) * (5 + r50); + v = classical ? Evaluation(pos).value() // classical + : adjusted_NNUE(); // NNUE } // Damp down the evaluation linearly when shuffling @@ -1061,7 +1126,7 @@ Value Eval::evaluate(const Position& pos) { /// descriptions and values of each evaluation term. Useful for debugging. /// Trace scores are from white's point of view -std::string Eval::trace(const Position& pos) { +std::string Eval::trace(Position& pos) { if (pos.checkers()) return "Final evaluation: none (in check)"; @@ -1073,44 +1138,55 @@ std::string Eval::trace(const Position& pos) { std::memset(scores, 0, sizeof(scores)); - pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt + pos.this_thread()->trend = SCORE_ZERO; // Reset any dynamic contempt v = Evaluation(pos).value(); ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) - << " Term | White | Black | Total \n" - << " | MG EG | MG EG | MG EG \n" - << " ------------+-------------+-------------+------------\n" - << " Material | " << Term(MATERIAL) - << " Imbalance | " << Term(IMBALANCE) - << " Pawns | " << Term(PAWN) - << " Knights | " << Term(KNIGHT) - << " Bishops | " << Term(BISHOP) - << " Rooks | " << Term(ROOK) - << " Queens | " << Term(QUEEN) - << " Mobility | " << Term(MOBILITY) - << " King safety | " << Term(KING) - << " Threats | " << Term(THREAT) - << " Passed | " << Term(PASSED) - << " Space | " << Term(SPACE) - << " Winnable | " << Term(WINNABLE) - << " ------------+-------------+-------------+------------\n" - << " Total | " << Term(TOTAL); + << " Contributing terms for the classical eval:\n" + << "+------------+-------------+-------------+-------------+\n" + << "| Term | White | Black | Total |\n" + << "| | MG EG | MG EG | MG EG |\n" + << "+------------+-------------+-------------+-------------+\n" + << "| Material | " << Term(MATERIAL) + << "| Imbalance | " << Term(IMBALANCE) + << "| Pawns | " << Term(PAWN) + << "| Knights | " << Term(KNIGHT) + << "| Bishops | " << Term(BISHOP) + << "| Rooks | " << Term(ROOK) + << "| Queens | " << Term(QUEEN) + << "| Mobility | " << Term(MOBILITY) + << "|King safety | " << Term(KING) + << "| Threats | " << Term(THREAT) + << "| Passed | " << Term(PASSED) + << "| Space | " << Term(SPACE) + << "| Winnable | " << Term(WINNABLE) + << "+------------+-------------+-------------+-------------+\n" + << "| Total | " << Term(TOTAL) + << "+------------+-------------+-------------+-------------+\n"; - v = pos.side_to_move() == WHITE ? v : -v; + if (Eval::useNNUE) + ss << '\n' << NNUE::trace(pos) << '\n'; - ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n"; + ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n"; if (Eval::useNNUE) { - v = NNUE::evaluate(pos); + v = NNUE::evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; - ss << "\nNNUE evaluation: " << to_cp(v) << " (white side)\n"; + ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; } v = evaluate(pos); v = pos.side_to_move() == WHITE ? v : -v; - ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n"; + ss << "Final evaluation " << to_cp(v) << " (white side)"; + if (Eval::useNNUE) + ss << " [with scaled NNUE, hybrid, ...]"; + ss << "\n"; return ss.str(); } + +} // namespace Stockfish diff --git a/engines/stockfish/evaluate.h b/engines/stockfish/evaluate.h index 8da0e30..54f20ba 100644 --- a/engines/stockfish/evaluate.h +++ b/engines/stockfish/evaluate.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,37 +20,43 @@ #define EVALUATE_H_INCLUDED #include +#include #include "types.h" +namespace Stockfish { + class Position; namespace Eval { - std::string trace(const Position& pos); + std::string trace(Position& pos); Value evaluate(const Position& pos); - extern int useNNUE; - extern int64_t nnueHits; - extern int64_t evalHits; + extern bool useNNUE; extern std::string eval_file_loaded; - void init_NNUE(); - void verify_NNUE(); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-03744f8d56d8.nnue" + #define EvalFileDefaultName "nn-9e3c6298299a.nnue" namespace NNUE { - Value evaluate(const Position& pos); - Value compute_eval(const Position& pos); - void update_eval(const Position& pos); - bool load_eval(std::string streamName, std::istream& stream); + std::string trace(Position& pos); + Value evaluate(const Position& pos, bool adjusted = false); + + void init(); + void verify(); + + bool load_eval(std::string name, std::istream& stream); + bool save_eval(std::ostream& stream); + bool save_eval(const std::optional& filename); } // namespace NNUE } // namespace Eval +} // namespace Stockfish + #endif // #ifndef EVALUATE_H_INCLUDED diff --git a/engines/stockfish/main.cpp b/engines/stockfish/main.cpp index 39d22af..ec058ea 100644 --- a/engines/stockfish/main.cpp +++ b/engines/stockfish/main.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,21 +21,40 @@ #include "bitboard.h" #include "endgame.h" #include "position.h" +#include "psqt.h" #include "search.h" +#include "syzygy/tbprobe.h" #include "thread.h" #include "tt.h" #include "uci.h" -#include "syzygy/tbprobe.h" -namespace PSQT { - void init(); -} +using namespace Stockfish; -void _stockfish_initialize() { //int main(int argc, char* argv[]) { +// // std::cout << engine_info() << std::endl; +// // CommandLine::init(argc, argv); - +// UCI::init(Options); +// Tune::init(); +// PSQT::init(); +// Bitboards::init(); +// Position::init(); +// Bitbases::init(); +// Endgames::init(); +// Threads.set(size_t(Options["Threads"])); +// Search::clear(); // After threads are up +// Eval::NNUE::init(); +// +// UCI::loop(argc, argv); +// +// Threads.set(0); +// return 0; +//} + + +// Added by BanksiaGUI +void _stockfish_initialize() { UCI::init(Options); Tune::init(); PSQT::init(); @@ -45,9 +64,5 @@ void _stockfish_initialize() { Endgames::init(); Threads.set(size_t(Options["Threads"])); Search::clear(); // After threads are up - Eval::init_NNUE(); - -// UCI::loop(argc, argv); -// Threads.set(0); -// return 0; + Eval::NNUE::init(); } diff --git a/engines/stockfish/material.cpp b/engines/stockfish/material.cpp index 870a5e1..9d17af2 100644 --- a/engines/stockfish/material.cpp +++ b/engines/stockfish/material.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,32 +24,39 @@ using namespace std; +namespace Stockfish { + namespace { + #define S(mg, eg) make_score(mg, eg) // Polynomial material imbalance parameters - constexpr int QuadraticOurs[][PIECE_TYPE_NB] = { - // OUR PIECES - // pair pawn knight bishop rook queen - {1438 }, // Bishop pair - { 40, 38 }, // Pawn - { 32, 255, -62 }, // Knight OUR PIECES - { 0, 104, 4, 0 }, // Bishop - { -26, -2, 47, 105, -208 }, // Rook - {-189, 24, 117, 133, -134, -6 } // Queen + // One Score parameter for each pair (our piece, another of our pieces) + constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = { + // OUR PIECE 2 + // bishop pair pawn knight bishop rook queen + {S(1419, 1455) }, // Bishop pair + {S( 101, 28), S( 37, 39) }, // Pawn + {S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1 + {S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop + {S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook + {S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen }; - constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = { - // THEIR PIECES - // pair pawn knight bishop rook queen - { }, // Bishop pair - { 36, }, // Pawn - { 9, 63, }, // Knight OUR PIECES - { 59, 65, 42, }, // Bishop - { 46, 39, 24, -24, }, // Rook - { 97, 100, -42, 137, 268, } // Queen + // One Score parameter for each pair (our piece, their piece) + constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = { + // THEIR PIECE + // bishop pair pawn knight bishop rook queen + { }, // Bishop pair + {S( 33, 30) }, // Pawn + {S( 46, 18), S(106, 84) }, // Knight OUR PIECE + {S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop + {S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook + {S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen }; + #undef S + // Endgame evaluation and scaling functions are accessed directly and not through // the function maps because they correspond to more than one material hash key. Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; @@ -67,7 +74,7 @@ namespace { bool is_KBPsK(const Position& pos, Color us) { return pos.non_pawn_material(us) == BishopValueMg - && pos.count(us) >= 1; + && pos.count(us) >= 1; } bool is_KQKRPs(const Position& pos, Color us) { @@ -82,11 +89,11 @@ namespace { /// piece type for both colors. template - int imbalance(const int pieceCount[][PIECE_TYPE_NB]) { + Score imbalance(const int pieceCount[][PIECE_TYPE_NB]) { constexpr Color Them = ~Us; - int bonus = 0; + Score bonus = SCORE_ZERO; // Second-degree polynomial material imbalance, by Tord Romstad for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) @@ -213,8 +220,10 @@ Entry* probe(const Position& pos) { { pos.count(BLACK) > 1, pos.count(BLACK), pos.count(BLACK), pos.count(BLACK) , pos.count(BLACK), pos.count(BLACK) } }; - e->value = int16_t((imbalance(pieceCount) - imbalance(pieceCount)) / 16); + e->score = (imbalance(pieceCount) - imbalance(pieceCount)) / 16; return e; } } // namespace Material + +} // namespace Stockfish diff --git a/engines/stockfish/material.h b/engines/stockfish/material.h index 80d0165..26535a5 100644 --- a/engines/stockfish/material.h +++ b/engines/stockfish/material.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ #include "position.h" #include "types.h" -namespace Material { +namespace Stockfish::Material { /// Material::Entry contains various information about a material configuration. /// It contains a material imbalance evaluation, a function pointer to a special @@ -37,8 +37,8 @@ namespace Material { struct Entry { - Score imbalance() const { return make_score(value, value); } - Phase game_phase() const { return gamePhase; } + Score imbalance() const { return score; } + Phase game_phase() const { return (Phase)gamePhase; } bool specialized_eval_exists() const { return evaluationFunction != nullptr; } Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } @@ -57,15 +57,15 @@ struct Entry { const EndgameBase* evaluationFunction; const EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each // side (e.g. KPKP, KBPsK) - int16_t value; + Score score; + int16_t gamePhase; uint8_t factor[COLOR_NB]; - Phase gamePhase; }; typedef HashTable Table; Entry* probe(const Position& pos); -} // namespace Material +} // namespace Stockfish::Material #endif // #ifndef MATERIAL_H_INCLUDED diff --git a/engines/stockfish/misc.cpp b/engines/stockfish/misc.cpp index d9bc47e..9e7b7e3 100644 --- a/engines/stockfish/misc.cpp +++ b/engines/stockfish/misc.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -51,7 +51,7 @@ typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); #include #endif -#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__) #define POSIXALIGNEDALLOC #include #endif @@ -61,6 +61,8 @@ typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); using namespace std; +namespace Stockfish { + namespace { /// Version number. If Version is left empty, then compile date in the format @@ -138,7 +140,7 @@ class Logger { /// the program was compiled) or "Stockfish ", depending on whether /// Version is empty. -const string engine_info(bool to_uci) { +string engine_info(bool to_uci) { const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); string month, day, year; @@ -161,7 +163,7 @@ const string engine_info(bool to_uci) { /// compiler_info() returns a string trying to describe the compiler we use -const std::string compiler_info() { +std::string compiler_info() { #define stringify2(x) #x #define stringify(x) stringify2(x) @@ -190,6 +192,18 @@ const std::string compiler_info() { compiler += "(version "; compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); compiler += ")"; + #elif defined(__e2k__) && defined(__LCC__) + #define dot_ver2(n) \ + compiler += (char)'.'; \ + compiler += (char)('0' + (n) / 10); \ + compiler += (char)('0' + (n) % 10); + + compiler += "MCST LCC "; + compiler += "(version "; + compiler += std::to_string(__LCC__ / 100); + dot_ver2(__LCC__ % 100) + dot_ver2(__LCC_MINOR__) + compiler += ")"; #elif __GNUC__ compiler += "g++ (GNUC) "; compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); @@ -361,7 +375,11 @@ void std_aligned_free(void* ptr) { #if defined(_WIN32) -static void* aligned_large_pages_alloc_win(size_t allocSize) { +static void* aligned_large_pages_alloc_windows(size_t allocSize) { + + #if !defined(_WIN64) + return nullptr; + #else HANDLE hProcessToken { }; LUID luid { }; @@ -404,26 +422,14 @@ static void* aligned_large_pages_alloc_win(size_t allocSize) { CloseHandle(hProcessToken); return mem; + + #endif } void* aligned_large_pages_alloc(size_t allocSize) { - static bool firstCall = true; - void* mem; - // Try to allocate large pages - mem = aligned_large_pages_alloc_win(allocSize); - - // Suppress info strings on the first call. The first call occurs before 'uci' - // is received and in that case this output confuses some GUIs. - if (!firstCall) - { - if (mem) - sync_cout << "info string Hash table allocation: Windows large pages used." << sync_endl; - else - sync_cout << "info string Hash table allocation: Windows large pages not used." << sync_endl; - } - firstCall = false; + void* mem = aligned_large_pages_alloc_windows(allocSize); // Fall back to regular, page aligned, allocation if necessary if (!mem) @@ -463,8 +469,9 @@ void aligned_large_pages_free(void* mem) { if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) { DWORD err = GetLastError(); - std::cerr << "Failed to free transposition table. Error code: 0x" << - std::hex << err << std::dec << std::endl; + std::cerr << "Failed to free large page memory. Error code: 0x" + << std::hex << err + << std::dec << std::endl; exit(EXIT_FAILURE); } } @@ -597,11 +604,10 @@ namespace CommandLine { string argv0; // path+name of the executable binary, as given by argv[0] string binaryDirectory; // path of the executable directory string workingDirectory; // path of the working directory -string pathSeparator; // Separator for our current OS void init(int argc, char* argv[]) { (void)argc; - string separator; + string pathSeparator; // extract the path+name of the executable binary argv0 = argv[0]; @@ -641,3 +647,5 @@ void init(int argc, char* argv[]) { } // namespace CommandLine + +} // namespace Stockfish diff --git a/engines/stockfish/misc.h b/engines/stockfish/misc.h index bc48f30..dae37cd 100644 --- a/engines/stockfish/misc.h +++ b/engines/stockfish/misc.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,11 +24,14 @@ #include #include #include +#include #include "types.h" -const std::string engine_info(bool to_uci = false); -const std::string compiler_info(); +namespace Stockfish { + +std::string engine_info(bool to_uci = false); +std::string compiler_info(); void prefetch(void* addr); void start_logger(const std::string& fname); void* std_aligned_alloc(size_t alignment, size_t size); @@ -64,6 +67,67 @@ std::ostream& operator<<(std::ostream&, SyncCout); #define sync_endl std::endl << IO_UNLOCK +// align_ptr_up() : get the first aligned element of an array. +// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, +// where N is the number of elements in the array. +template +T* align_ptr_up(T* ptr) +{ + static_assert(alignof(T) < Alignment); + + const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); + return reinterpret_cast(reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); +} + + +// IsLittleEndian : true if and only if the binary is compiled on a little endian machine +static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; +static inline const bool IsLittleEndian = (Le.c[0] == 4); + + +template +class ValueListInserter { +public: + ValueListInserter(T* v, std::size_t& s) : + values(v), + size(&s) + { + } + + void push_back(const T& value) { values[(*size)++] = value; } +private: + T* values; + std::size_t* size; +}; + +template +class ValueList { + +public: + std::size_t size() const { return size_; } + void resize(std::size_t newSize) { size_ = newSize; } + void push_back(const T& value) { values_[size_++] = value; } + T& operator[](std::size_t index) { return values_[index]; } + T* begin() { return values_; } + T* end() { return values_ + size_; } + const T& operator[](std::size_t index) const { return values_[index]; } + const T* begin() const { return values_; } + const T* end() const { return values_ + size_; } + operator ValueListInserter() { return ValueListInserter(values_, size_); } + + void swap(ValueList& other) { + const std::size_t maxSize = std::max(size_, other.size_); + for (std::size_t i = 0; i < maxSize; ++i) { + std::swap(values_[i], other.values_[i]); + } + std::swap(size_, other.size_); + } + +private: + T values_[MaxSize]; + std::size_t size_ = 0; +}; + /// xorshift64star Pseudo-Random Number Generator /// This class is based on original code written and dedicated /// to the public domain by Sebastiano Vigna (2014). @@ -131,4 +195,6 @@ namespace CommandLine { extern std::string workingDirectory; // path of the working directory } +} // namespace Stockfish + #endif // #ifndef MISC_H_INCLUDED diff --git a/engines/stockfish/movegen.cpp b/engines/stockfish/movegen.cpp index 3340f65..5f3ba90 100644 --- a/engines/stockfish/movegen.cpp +++ b/engines/stockfish/movegen.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,24 +21,21 @@ #include "movegen.h" #include "position.h" +namespace Stockfish { + namespace { template - ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) { + ExtMove* make_promotions(ExtMove* moveList, Square to) { if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) - { *moveList++ = make(to - D, to, QUEEN); - if (attacks_bb(to) & ksq) - *moveList++ = make(to - D, to, KNIGHT); - } if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) { *moveList++ = make(to - D, to, ROOK); *moveList++ = make(to - D, to, BISHOP); - if (!(attacks_bb(to) & ksq)) - *moveList++ = make(to - D, to, KNIGHT); + *moveList++ = make(to - D, to, KNIGHT); } return moveList; @@ -55,20 +52,16 @@ namespace { constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); - const Square ksq = pos.square(Them); - Bitboard emptySquares; + const Bitboard emptySquares = Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces(); + const Bitboard enemies = Type == EVASIONS ? pos.checkers() + : Type == CAPTURES ? target : pos.pieces(Them); Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; - Bitboard enemies = (Type == EVASIONS ? pos.pieces(Them) & target: - Type == CAPTURES ? target : pos.pieces(Them)); - // Single and double pawn pushes, no promotions if (Type != CAPTURES) { - emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces()); - Bitboard b1 = shift(pawnsNotOn7) & emptySquares; Bitboard b2 = shift(b1 & TRank3BB) & emptySquares; @@ -80,33 +73,24 @@ namespace { if (Type == QUIET_CHECKS) { - b1 &= pawn_attacks_bb(Them, ksq); - b2 &= pawn_attacks_bb(Them, ksq); - - // Add pawn pushes which give discovered check. This is possible only - // if the pawn is not on the same file as the enemy king, because we - // don't generate captures. Note that a possible discovery check - // promotion has been already generated amongst the captures. - Bitboard dcCandidateQuiets = pos.blockers_for_king(Them) & pawnsNotOn7; - if (dcCandidateQuiets) - { - Bitboard dc1 = shift(dcCandidateQuiets) & emptySquares & ~file_bb(ksq); - Bitboard dc2 = shift(dc1 & TRank3BB) & emptySquares; - - b1 |= dc1; - b2 |= dc2; - } + // To make a quiet check, you either make a direct check by pushing a pawn + // or push a blocker pawn that is not on the same file as the enemy king. + // Discovered check promotion has been already generated amongst the captures. + Square ksq = pos.square(Them); + Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq); + b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns); + b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); } while (b1) { - Square to = pop_lsb(&b1); + Square to = pop_lsb(b1); *moveList++ = make_move(to - Up, to); } while (b2) { - Square to = pop_lsb(&b2); + Square to = pop_lsb(b2); *moveList++ = make_move(to - Up - Up, to); } } @@ -114,27 +98,24 @@ namespace { // Promotions and underpromotions if (pawnsOn7) { - if (Type == CAPTURES) - emptySquares = ~pos.pieces(); - - if (Type == EVASIONS) - emptySquares &= target; - Bitboard b1 = shift(pawnsOn7) & enemies; Bitboard b2 = shift(pawnsOn7) & enemies; Bitboard b3 = shift(pawnsOn7) & emptySquares; + if (Type == EVASIONS) + b3 &= target; + while (b1) - moveList = make_promotions(moveList, pop_lsb(&b1), ksq); + moveList = make_promotions(moveList, pop_lsb(b1)); while (b2) - moveList = make_promotions(moveList, pop_lsb(&b2), ksq); + moveList = make_promotions(moveList, pop_lsb(b2)); while (b3) - moveList = make_promotions(moveList, pop_lsb(&b3), ksq); + moveList = make_promotions(moveList, pop_lsb(b3)); } - // Standard and en-passant captures + // Standard and en passant captures if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { Bitboard b1 = shift(pawnsNotOn7) & enemies; @@ -142,13 +123,13 @@ namespace { while (b1) { - Square to = pop_lsb(&b1); + Square to = pop_lsb(b1); *moveList++ = make_move(to - UpRight, to); } while (b2) { - Square to = pop_lsb(&b2); + Square to = pop_lsb(b2); *moveList++ = make_move(to - UpLeft, to); } @@ -156,10 +137,8 @@ namespace { { assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6)); - // An en passant capture can be an evasion only if the checking piece - // is the double pushed pawn and so is in the target. Otherwise this - // is a discovery check and we are forced to do otherwise. - if (Type == EVASIONS && !(target & (pos.ep_square() - Up))) + // An en passant capture cannot resolve a discovered check + if (Type == EVASIONS && (target & (pos.ep_square() + Up))) return moveList; b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square()); @@ -167,7 +146,7 @@ namespace { assert(b1); while (b1) - *moveList++ = make(pop_lsb(&b1), pos.ep_square()); + *moveList++ = make(pop_lsb(b1), pos.ep_square()); } } @@ -180,27 +159,19 @@ namespace { static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); - const Square* pl = pos.squares(Us); + Bitboard bb = pos.pieces(Us, Pt); - for (Square from = *pl; from != SQ_NONE; from = *++pl) + while (bb) { - if (Checks) - { - if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN) - && !(attacks_bb(from) & target & pos.check_squares(Pt))) - continue; - - if (pos.blockers_for_king(~Us) & from) - continue; - } - + Square from = pop_lsb(bb); Bitboard b = attacks_bb(from, pos.pieces()) & target; - if (Checks) + // To check, you either move freely a blocker or make a direct check. + if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from))) b &= pos.check_squares(Pt); while (b) - *moveList++ = make_move(from, pop_lsb(&b)); + *moveList++ = make_move(from, pop_lsb(b)); } return moveList; @@ -209,45 +180,38 @@ namespace { template ExtMove* generate_all(const Position& pos, ExtMove* moveList) { - constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations + + static_assert(Type != LEGAL, "Unsupported type in generate_all()"); + + constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations + const Square ksq = pos.square(Us); Bitboard target; - switch (Type) + // Skip generating non-king moves when in double check + if (Type != EVASIONS || !more_than_one(pos.checkers())) { - case CAPTURES: - target = pos.pieces(~Us); - break; - case QUIETS: - case QUIET_CHECKS: - target = ~pos.pieces(); - break; - case EVASIONS: - { - Square checksq = lsb(pos.checkers()); - target = between_bb(pos.square(Us), checksq) | checksq; - break; - } - case NON_EVASIONS: - target = ~pos.pieces(Us); - break; - default: - static_assert(true, "Unsupported type in generate_all()"); + target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) + : Type == NON_EVASIONS ? ~pos.pieces( Us) + : Type == CAPTURES ? pos.pieces(~Us) + : ~pos.pieces( ); // QUIETS || QUIET_CHECKS + + moveList = generate_pawn_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); } - moveList = generate_pawn_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - - if (Type != QUIET_CHECKS && Type != EVASIONS) + if (!Checks || pos.blockers_for_king(~Us) & ksq) { - Square ksq = pos.square(Us); - Bitboard b = attacks_bb(ksq) & target; + Bitboard b = attacks_bb(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target); + if (Checks) + b &= ~attacks_bb(pos.square(~Us)); + while (b) - *moveList++ = make_move(ksq, pop_lsb(&b)); + *moveList++ = make_move(ksq, pop_lsb(b)); - if ((Type != CAPTURES) && pos.can_castle(Us & ANY_CASTLING)) + if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) *moveList++ = make(ksq, pos.castling_rook_square(cr)); @@ -259,8 +223,10 @@ namespace { } // namespace -/// Generates all pseudo-legal captures plus queen and checking knight promotions -/// Generates all pseudo-legal non-captures and underpromotions(except checking knight) +/// Generates all pseudo-legal captures plus queen promotions +/// Generates all pseudo-legal non-captures and underpromotions +/// Generates all pseudo-legal check evasions when the side to move is in check +/// Generates all pseudo-legal non-captures giving check, except castling and promotions /// Generates all pseudo-legal captures and non-captures /// /// Returns a pointer to the end of the move list. @@ -268,8 +234,8 @@ namespace { template ExtMove* generate(const Position& pos, ExtMove* moveList) { - static_assert(Type == CAPTURES || Type == QUIETS || Type == NON_EVASIONS, "Unsupported type in generate()"); - assert(!pos.checkers()); + static_assert(Type != LEGAL, "Unsupported type in generate()"); + assert((Type == EVASIONS) == (bool)pos.checkers()); Color us = pos.side_to_move(); @@ -280,70 +246,11 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { // Explicit template instantiations template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); -/// generate generates all pseudo-legal non-captures. -/// Returns a pointer to the end of the move list. -template<> -ExtMove* generate(const Position& pos, ExtMove* moveList) { - - assert(!pos.checkers()); - - Color us = pos.side_to_move(); - Bitboard dc = pos.blockers_for_king(~us) & pos.pieces(us) & ~pos.pieces(PAWN); - - while (dc) - { - Square from = pop_lsb(&dc); - PieceType pt = type_of(pos.piece_on(from)); - - Bitboard b = attacks_bb(pt, from, pos.pieces()) & ~pos.pieces(); - - if (pt == KING) - b &= ~attacks_bb(pos.square(~us)); - - while (b) - *moveList++ = make_move(from, pop_lsb(&b)); - } - - return us == WHITE ? generate_all(pos, moveList) - : generate_all(pos, moveList); -} - - -/// generate generates all pseudo-legal check evasions when the side -/// to move is in check. Returns a pointer to the end of the move list. -template<> -ExtMove* generate(const Position& pos, ExtMove* moveList) { - - assert(pos.checkers()); - - Color us = pos.side_to_move(); - Square ksq = pos.square(us); - Bitboard sliderAttacks = 0; - Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN); - - // Find all the squares attacked by slider checkers. We will remove them from - // the king evasions in order to skip known illegal moves, which avoids any - // useless legality checks later on. - while (sliders) - sliderAttacks |= line_bb(ksq, pop_lsb(&sliders)) & ~pos.checkers(); - - // Generate evasions for king, capture and non capture moves - Bitboard b = attacks_bb(ksq) & ~pos.pieces(us) & ~sliderAttacks; - while (b) - *moveList++ = make_move(ksq, pop_lsb(&b)); - - if (more_than_one(pos.checkers())) - return moveList; // Double check, only a king move can save the day - - // Generate blocking evasions or captures of the checking piece - return us == WHITE ? generate_all(pos, moveList) - : generate_all(pos, moveList); -} - - /// generate generates all the legal moves in the given position template<> @@ -357,7 +264,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); while (cur != moveList) - if ( (pinned || from_sq(*cur) == ksq || type_of(*cur) == ENPASSANT) + if ( ((pinned && pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) && !pos.legal(*cur)) *cur = (--moveList)->move; else @@ -365,3 +272,5 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { return moveList; } + +} // namespace Stockfish diff --git a/engines/stockfish/movegen.h b/engines/stockfish/movegen.h index fb616d0..3f895f0 100644 --- a/engines/stockfish/movegen.h +++ b/engines/stockfish/movegen.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ #include "types.h" +namespace Stockfish { + class Position; enum GenType { @@ -70,4 +72,6 @@ struct MoveList { ExtMove moveList[MAX_MOVES], *last; }; +} // namespace Stockfish + #endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/engines/stockfish/movepick.cpp b/engines/stockfish/movepick.cpp index 153d323..4ff4cff 100644 --- a/engines/stockfish/movepick.cpp +++ b/engines/stockfish/movepick.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,8 @@ #include "movepick.h" +namespace Stockfish { + namespace { enum Stages { @@ -73,8 +75,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist assert(d <= 0); stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + - !(ttm && (depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare) - && pos.pseudo_legal(ttm)); + !( ttm + && (pos.checkers() || depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare) + && pos.pseudo_legal(ttm)); } /// MovePicker constructor for ProbCut: we generate captures with SEE greater @@ -98,15 +101,15 @@ void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); for (auto& m : *this) - if (Type == CAPTURES) + if constexpr (Type == CAPTURES) m.value = int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6 + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; - else if (Type == QUIETS) + else if constexpr (Type == QUIETS) m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + 2 * (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] - + 2 * (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] + + (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] + + (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] + (ply < MAX_LPH ? std::min(4, depth / 3) * (*lowPlyHistory)[ply][from_to(m)] : 0); @@ -116,8 +119,8 @@ void MovePicker::score() { m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))); else - m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - (1 << 28); } } @@ -141,7 +144,7 @@ Move MovePicker::select(Pred filter) { } /// MovePicker::next_move() is the most important method of the MovePicker class. It -/// returns a new pseudo legal move every time it is called until there are no more +/// returns a new pseudo-legal move every time it is called until there are no more /// moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { @@ -262,3 +265,5 @@ Move MovePicker::next_move(bool skipQuiets) { assert(false); return MOVE_NONE; // Silence warning } + +} // namespace Stockfish diff --git a/engines/stockfish/movepick.h b/engines/stockfish/movepick.h index 4c0ad55..c76d495 100644 --- a/engines/stockfish/movepick.h +++ b/engines/stockfish/movepick.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,6 +27,8 @@ #include "position.h" #include "types.h" +namespace Stockfish { + /// StatsEntry stores the stat table value. It is usually a number but could /// be a move or even a nested history. We use a class instead of naked value /// to directly call history update operator<<() on the entry so to use stats @@ -84,7 +86,7 @@ enum StatsType { NoCaptures, Captures }; /// unsuccessful during the current search, and is used for reduction and move /// ordering decisions. It uses 2 tables (one for each color) indexed by /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards -typedef Stats ButterflyHistory; +typedef Stats ButterflyHistory; /// At higher depths LowPlyHistory records successful quiet moves near the root /// and quiet moves which are/were in the PV (ttPv). It is cleared with each new @@ -108,12 +110,12 @@ typedef Stats PieceToHistory; typedef Stats ContinuationHistory; -/// MovePicker class is used to pick one pseudo legal move at a time from the +/// MovePicker class is used to pick one pseudo-legal move at a time from the /// current position. The most important method is next_move(), which returns a -/// new pseudo legal move each time it is called, until there are no moves left, -/// when MOVE_NONE is returned. In order to improve the efficiency of the alpha -/// beta algorithm, MovePicker attempts to return the moves which are most likely -/// to get a cut-off first. +/// new pseudo-legal move each time it is called, until there are no moves left, +/// when MOVE_NONE is returned. In order to improve the efficiency of the +/// alpha-beta algorithm, MovePicker attempts to return the moves which are most +/// likely to get a cut-off first. class MovePicker { enum PickType { Next, Best }; @@ -156,4 +158,6 @@ class MovePicker { ExtMove moves[MAX_MOVES]; }; +} // namespace Stockfish + #endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/engines/stockfish/nnue/evaluate_nnue.cpp b/engines/stockfish/nnue/evaluate_nnue.cpp index 72d1820..891f8fa 100644 --- a/engines/stockfish/nnue/evaluate_nnue.cpp +++ b/engines/stockfish/nnue/evaluate_nnue.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,58 +20,42 @@ #include #include +#include +#include +#include #include "../evaluate.h" #include "../position.h" #include "../misc.h" #include "../uci.h" +#include "../types.h" #include "evaluate_nnue.h" -namespace Eval::NNUE { - - uint32_t kpp_board_index[PIECE_NB][COLOR_NB] = { - // convention: W - us, B - them - // viewed from other side, W and B are reversed - { PS_NONE, PS_NONE }, - { PS_W_PAWN, PS_B_PAWN }, - { PS_W_KNIGHT, PS_B_KNIGHT }, - { PS_W_BISHOP, PS_B_BISHOP }, - { PS_W_ROOK, PS_B_ROOK }, - { PS_W_QUEEN, PS_B_QUEEN }, - { PS_W_KING, PS_B_KING }, - { PS_NONE, PS_NONE }, - { PS_NONE, PS_NONE }, - { PS_B_PAWN, PS_W_PAWN }, - { PS_B_KNIGHT, PS_W_KNIGHT }, - { PS_B_BISHOP, PS_W_BISHOP }, - { PS_B_ROOK, PS_W_ROOK }, - { PS_B_QUEEN, PS_W_QUEEN }, - { PS_B_KING, PS_W_KING }, - { PS_NONE, PS_NONE } - }; +namespace Stockfish::Eval::NNUE { // Input feature converter - LargePagePtr feature_transformer; + LargePagePtr featureTransformer; // Evaluation function - AlignedPtr network; + AlignedPtr network[LayerStacks]; // Evaluation function file name std::string fileName; + std::string netDescription; namespace Detail { // Initialize the evaluation function parameters template - void Initialize(AlignedPtr& pointer) { + void initialize(AlignedPtr& pointer) { pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); std::memset(pointer.get(), 0, sizeof(T)); } template - void Initialize(LargePagePtr& pointer) { + void initialize(LargePagePtr& pointer) { static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); @@ -80,67 +64,357 @@ namespace Eval::NNUE { // Read evaluation function parameters template - bool ReadParameters(std::istream& stream, T& reference) { + bool read_parameters(std::istream& stream, T& reference) { std::uint32_t header; header = read_little_endian(stream); - if (!stream || header != T::GetHashValue()) return false; - return reference.ReadParameters(stream); + if (!stream || header != T::get_hash_value()) return false; + return reference.read_parameters(stream); + } + + // Write evaluation function parameters + template + bool write_parameters(std::ostream& stream, const T& reference) { + + write_little_endian(stream, T::get_hash_value()); + return reference.write_parameters(stream); } } // namespace Detail // Initialize the evaluation function parameters - void Initialize() { + void initialize() { - Detail::Initialize(feature_transformer); - Detail::Initialize(network); + Detail::initialize(featureTransformer); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(network[i]); } // Read network header - bool ReadHeader(std::istream& stream, std::uint32_t* hash_value, std::string* architecture) + bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) { std::uint32_t version, size; version = read_little_endian(stream); - *hash_value = read_little_endian(stream); + *hashValue = read_little_endian(stream); size = read_little_endian(stream); - if (!stream || version != kVersion) return false; - architecture->resize(size); - stream.read(&(*architecture)[0], size); + if (!stream || version != Version) return false; + desc->resize(size); + stream.read(&(*desc)[0], size); + return !stream.fail(); + } + + // Write network header + bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) + { + write_little_endian(stream, Version); + write_little_endian(stream, hashValue); + write_little_endian(stream, desc.size()); + stream.write(&desc[0], desc.size()); return !stream.fail(); } // Read network parameters - bool ReadParameters(std::istream& stream) { - - std::uint32_t hash_value; - std::string architecture; - if (!ReadHeader(stream, &hash_value, &architecture)) return false; - if (hash_value != kHashValue) return false; - if (!Detail::ReadParameters(stream, *feature_transformer)) return false; - if (!Detail::ReadParameters(stream, *network)) return false; + bool read_parameters(std::istream& stream) { + + std::uint32_t hashValue; + if (!read_header(stream, &hashValue, &netDescription)) return false; + if (hashValue != HashValue) return false; + if (!Detail::read_parameters(stream, *featureTransformer)) return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + if (!Detail::read_parameters(stream, *(network[i]))) return false; return stream && stream.peek() == std::ios::traits_type::eof(); } + // Write network parameters + bool write_parameters(std::ostream& stream) { + + if (!write_header(stream, HashValue, netDescription)) return false; + if (!Detail::write_parameters(stream, *featureTransformer)) return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + if (!Detail::write_parameters(stream, *(network[i]))) return false; + return (bool)stream; + } + // Evaluation function. Perform differential calculation. - Value evaluate(const Position& pos) { + Value evaluate(const Position& pos, bool adjusted) { + + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + + constexpr uint64_t alignment = CacheLineSize; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType transformedFeaturesUnaligned[ + FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; + char bufferUnaligned[Network::BufferSize + alignment]; - alignas(kCacheLineSize) TransformedFeatureType - transformed_features[FeatureTransformer::kBufferSize]; - feature_transformer->Transform(pos, transformed_features); - alignas(kCacheLineSize) char buffer[Network::kBufferSize]; - const auto output = network->Propagate(transformed_features, buffer); + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); + auto* buffer = align_ptr_up(&bufferUnaligned[0]); +#else + alignas(alignment) + TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) char buffer[Network::BufferSize]; +#endif - return static_cast(output[0] / FV_SCALE); + ASSERT_ALIGNED(transformedFeatures, alignment); + ASSERT_ALIGNED(buffer, alignment); + + const std::size_t bucket = (pos.count() - 1) / 4; + const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); + const auto output = network[bucket]->propagate(transformedFeatures, buffer); + + int materialist = psqt; + int positional = output[0]; + + int delta_npm = abs(pos.non_pawn_material(WHITE) - pos.non_pawn_material(BLACK)); + int entertainment = (adjusted && delta_npm <= BishopValueMg - KnightValueMg ? 7 : 0); + + int A = 128 - entertainment; + int B = 128 + entertainment; + + int sum = (A * materialist + B * positional) / 128; + + return static_cast( sum / OutputScale ); } + struct NnueEvalTrace { + static_assert(LayerStacks == PSQTBuckets); + + Value psqt[LayerStacks]; + Value positional[LayerStacks]; + std::size_t correctBucket; + }; + + static NnueEvalTrace trace_evaluate(const Position& pos) { + + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + + constexpr uint64_t alignment = CacheLineSize; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType transformedFeaturesUnaligned[ + FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; + char bufferUnaligned[Network::BufferSize + alignment]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); + auto* buffer = align_ptr_up(&bufferUnaligned[0]); +#else + alignas(alignment) + TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) char buffer[Network::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + ASSERT_ALIGNED(buffer, alignment); + + NnueEvalTrace t{}; + t.correctBucket = (pos.count() - 1) / 4; + for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { + const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); + const auto output = network[bucket]->propagate(transformedFeatures, buffer); + + int materialist = psqt; + int positional = output[0]; + + t.psqt[bucket] = static_cast( materialist / OutputScale ); + t.positional[bucket] = static_cast( positional / OutputScale ); + } + + return t; + } + + static const std::string PieceToChar(" PNBRQK pnbrqk"); + + + // format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer. + // The buffer must have capacity for at least 5 chars. + static void format_cp_compact(Value v, char* buffer) { + + buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); + + int cp = std::abs(100 * v / PawnValueEg); + if (cp >= 10000) + { + buffer[1] = '0' + cp / 10000; cp %= 10000; + buffer[2] = '0' + cp / 1000; cp %= 1000; + buffer[3] = '0' + cp / 100; cp %= 100; + buffer[4] = ' '; + } + else if (cp >= 1000) + { + buffer[1] = '0' + cp / 1000; cp %= 1000; + buffer[2] = '0' + cp / 100; cp %= 100; + buffer[3] = '.'; + buffer[4] = '0' + cp / 10; + } + else + { + buffer[1] = '0' + cp / 100; cp %= 100; + buffer[2] = '.'; + buffer[3] = '0' + cp / 10; cp %= 10; + buffer[4] = '0' + cp / 1; + } + } + + + // format_cp_aligned_dot() converts a Value into (centi)pawns and writes it in a buffer, + // always keeping two decimals. The buffer must have capacity for at least 7 chars. + static void format_cp_aligned_dot(Value v, char* buffer) { + + buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); + + double cp = 1.0 * std::abs(int(v)) / PawnValueEg; + sprintf(&buffer[1], "%6.2f", cp); + } + + + // trace() returns a string with the value of each piece on a board, + // and a table for (PSQT, Layers) values bucket by bucket. + + std::string trace(Position& pos) { + + std::stringstream ss; + + char board[3*8+1][8*8+2]; + std::memset(board, ' ', sizeof(board)); + for (int row = 0; row < 3*8+1; ++row) + board[row][8*8+1] = '\0'; + + // A lambda to output one box of the board + auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { + + const int x = ((int)file) * 8; + const int y = (7 - (int)rank) * 3; + for (int i = 1; i < 8; ++i) + board[y][x+i] = board[y+3][x+i] = '-'; + for (int i = 1; i < 3; ++i) + board[y+i][x] = board[y+i][x+8] = '|'; + board[y][x] = board[y][x+8] = board[y+3][x+8] = board[y+3][x] = '+'; + if (pc != NO_PIECE) + board[y+1][x+4] = PieceToChar[pc]; + if (value != VALUE_NONE) + format_cp_compact(value, &board[y+2][x+2]); + }; + + // We estimate the value of each piece by doing a differential evaluation from + // the current base eval, simulating the removal of the piece from its square. + Value base = evaluate(pos); + base = pos.side_to_move() == WHITE ? base : -base; + + for (File f = FILE_A; f <= FILE_H; ++f) + for (Rank r = RANK_1; r <= RANK_8; ++r) + { + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Value v = VALUE_NONE; + + if (pc != NO_PIECE && type_of(pc) != KING) + { + auto st = pos.state(); + + pos.remove_piece(sq); + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + + Value eval = evaluate(pos); + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq); + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + } + + writeSquare(f, r, pc, v); + } + + ss << " NNUE derived piece values:\n"; + for (int row = 0; row < 3*8+1; ++row) + ss << board[row] << '\n'; + ss << '\n'; + + auto t = trace_evaluate(pos); + + ss << " NNUE network contributions " + << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl + << "+------------+------------+------------+------------+\n" + << "| Bucket | Material | Positional | Total |\n" + << "| | (PSQT) | (Layers) | |\n" + << "+------------+------------+------------+------------+\n"; + + for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) + { + char buffer[3][8]; + std::memset(buffer, '\0', sizeof(buffer)); + + format_cp_aligned_dot(t.psqt[bucket], buffer[0]); + format_cp_aligned_dot(t.positional[bucket], buffer[1]); + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], buffer[2]); + + ss << "| " << bucket << " " + << " | " << buffer[0] << " " + << " | " << buffer[1] << " " + << " | " << buffer[2] << " " + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; + } + + ss << "+------------+------------+------------+------------+\n"; + + return ss.str(); + } + + // Load eval, from a file stream or a memory stream - bool load_eval(std::string streamName, std::istream& stream) { + bool load_eval(std::string name, std::istream& stream) { + + initialize(); + fileName = name; + return read_parameters(stream); + } + + // Save eval, to a file stream or a memory stream + bool save_eval(std::ostream& stream) { + + if (fileName.empty()) + return false; + + return write_parameters(stream); + } - Initialize(); - fileName = streamName; - return ReadParameters(stream); + /// Save eval, to a file given by its name + bool save_eval(const std::optional& filename) { + + std::string actualFilename; + std::string msg; + + if (filename.has_value()) + actualFilename = filename.value(); + else + { + if (eval_file_loaded != EvalFileDefaultName) + { + msg = "Failed to export a net. A non-embedded net can only be saved if the filename is specified"; + + sync_cout << msg << sync_endl; + return false; + } + actualFilename = EvalFileDefaultName; + } + + std::ofstream stream(actualFilename, std::ios_base::binary); + bool saved = save_eval(stream); + + msg = saved ? "Network saved successfully to " + actualFilename + : "Failed to export a net"; + + sync_cout << msg << sync_endl; + return saved; } -} // namespace Eval::NNUE + +} // namespace Stockfish::Eval::NNUE diff --git a/engines/stockfish/nnue/evaluate_nnue.h b/engines/stockfish/nnue/evaluate_nnue.h index 459a93d..c7fa4a9 100644 --- a/engines/stockfish/nnue/evaluate_nnue.h +++ b/engines/stockfish/nnue/evaluate_nnue.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,11 +25,11 @@ #include -namespace Eval::NNUE { +namespace Stockfish::Eval::NNUE { // Hash value of evaluation function structure - constexpr std::uint32_t kHashValue = - FeatureTransformer::GetHashValue() ^ Network::GetHashValue(); + constexpr std::uint32_t HashValue = + FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); // Deleter for automating release of memory area template @@ -41,7 +41,7 @@ namespace Eval::NNUE { }; template - struct TtmemDeleter { + struct LargePageDeleter { void operator()(T* ptr) const { ptr->~T(); aligned_large_pages_free(ptr); @@ -52,8 +52,8 @@ namespace Eval::NNUE { using AlignedPtr = std::unique_ptr>; template - using LargePagePtr = std::unique_ptr>; + using LargePagePtr = std::unique_ptr>; -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/engines/stockfish/nnue/features/half_ka_v2.cpp b/engines/stockfish/nnue/features/half_ka_v2.cpp new file mode 100644 index 0000000..57f43e5 --- /dev/null +++ b/engines/stockfish/nnue/features/half_ka_v2.cpp @@ -0,0 +1,85 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKAv2 of NNUE evaluation function + +#include "half_ka_v2.h" + +#include "../../position.h" + +namespace Stockfish::Eval::NNUE::Features { + + // Orient a square according to perspective (rotates by 180 for black) + inline Square HalfKAv2::orient(Color perspective, Square s) { + return Square(int(s) ^ (bool(perspective) * 56)); + } + + // Index of a feature for a given king position and another piece on some square + inline IndexType HalfKAv2::make_index(Color perspective, Square s, Piece pc, Square ksq) { + return IndexType(orient(perspective, s) + PieceSquareIndex[perspective][pc] + PS_NB * ksq); + } + + // Get a list of indices for active features + void HalfKAv2::append_active_indices( + const Position& pos, + Color perspective, + ValueListInserter active + ) { + Square ksq = orient(perspective, pos.square(perspective)); + Bitboard bb = pos.pieces(); + while (bb) + { + Square s = pop_lsb(bb); + active.push_back(make_index(perspective, s, pos.piece_on(s), ksq)); + } + } + + + // append_changed_indices() : get a list of indices for recently changed features + + void HalfKAv2::append_changed_indices( + Square ksq, + StateInfo* st, + Color perspective, + ValueListInserter removed, + ValueListInserter added + ) { + const auto& dp = st->dirtyPiece; + Square oriented_ksq = orient(perspective, ksq); + for (int i = 0; i < dp.dirty_num; ++i) { + Piece pc = dp.piece[i]; + if (dp.from[i] != SQ_NONE) + removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq)); + if (dp.to[i] != SQ_NONE) + added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq)); + } + } + + int HalfKAv2::update_cost(StateInfo* st) { + return st->dirtyPiece.dirty_num; + } + + int HalfKAv2::refresh_cost(const Position& pos) { + return pos.count(); + } + + bool HalfKAv2::requires_refresh(StateInfo* st, Color perspective) { + return st->dirtyPiece.piece[0] == make_piece(perspective, KING); + } + +} // namespace Stockfish::Eval::NNUE::Features diff --git a/engines/stockfish/nnue/features/half_ka_v2.h b/engines/stockfish/nnue/features/half_ka_v2.h new file mode 100644 index 0000000..e4b2edd --- /dev/null +++ b/engines/stockfish/nnue/features/half_ka_v2.h @@ -0,0 +1,111 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKP of NNUE evaluation function + +#ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED +#define NNUE_FEATURES_HALF_KA_V2_H_INCLUDED + +#include "../nnue_common.h" + +#include "../../evaluate.h" +#include "../../misc.h" + +namespace Stockfish { + struct StateInfo; +} + +namespace Stockfish::Eval::NNUE::Features { + + // Feature HalfKAv2: Combination of the position of own king + // and the position of pieces + class HalfKAv2 { + + // unique number for each piece type on each square + enum { + PS_NONE = 0, + PS_W_PAWN = 0, + PS_B_PAWN = 1 * SQUARE_NB, + PS_W_KNIGHT = 2 * SQUARE_NB, + PS_B_KNIGHT = 3 * SQUARE_NB, + PS_W_BISHOP = 4 * SQUARE_NB, + PS_B_BISHOP = 5 * SQUARE_NB, + PS_W_ROOK = 6 * SQUARE_NB, + PS_B_ROOK = 7 * SQUARE_NB, + PS_W_QUEEN = 8 * SQUARE_NB, + PS_B_QUEEN = 9 * SQUARE_NB, + PS_KING = 10 * SQUARE_NB, + PS_NB = 11 * SQUARE_NB + }; + + static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = { + // convention: W - us, B - them + // viewed from other side, W and B are reversed + { PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE }, + { PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE } + }; + + // Orient a square according to perspective (rotates by 180 for black) + static Square orient(Color perspective, Square s); + + // Index of a feature for a given king position and another piece on some square + static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq); + + public: + // Feature name + static constexpr const char* Name = "HalfKAv2(Friend)"; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t HashValue = 0x5f234cb8u; + + // Number of feature dimensions + static constexpr IndexType Dimensions = + static_cast(SQUARE_NB) * static_cast(PS_NB); + + // Maximum number of simultaneously active features. + static constexpr IndexType MaxActiveDimensions = 32; + + // Get a list of indices for active features + static void append_active_indices( + const Position& pos, + Color perspective, + ValueListInserter active); + + // Get a list of indices for recently changed features + static void append_changed_indices( + Square ksq, + StateInfo* st, + Color perspective, + ValueListInserter removed, + ValueListInserter added); + + // Returns the cost of updating one perspective, the most costly one. + // Assumes no refresh needed. + static int update_cost(StateInfo* st); + static int refresh_cost(const Position& pos); + + // Returns whether the change stored in this StateInfo means that + // a full accumulator refresh is required. + static bool requires_refresh(StateInfo* st, Color perspective); + }; + +} // namespace Stockfish::Eval::NNUE::Features + +#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED diff --git a/engines/stockfish/nnue/layers/affine_transform.h b/engines/stockfish/nnue/layers/affine_transform.h index 94d0b5a..9a3b778 100644 --- a/engines/stockfish/nnue/layers/affine_transform.h +++ b/engines/stockfish/nnue/layers/affine_transform.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,10 +24,10 @@ #include #include "../nnue_common.h" -namespace Eval::NNUE::Layers { +namespace Stockfish::Eval::NNUE::Layers { // Affine transformation layer - template + template class AffineTransform { public: // Input/output type @@ -36,217 +36,387 @@ namespace Eval::NNUE::Layers { static_assert(std::is_same::value, ""); // Number of input/output dimensions - static constexpr IndexType kInputDimensions = - PreviousLayer::kOutputDimensions; - static constexpr IndexType kOutputDimensions = OutputDimensions; - static constexpr IndexType kPaddedInputDimensions = - CeilToMultiple(kInputDimensions, kMaxSimdWidth); + static constexpr IndexType InputDimensions = + PreviousLayer::OutputDimensions; + static constexpr IndexType OutputDimensions = OutDims; + static constexpr IndexType PaddedInputDimensions = + ceil_to_multiple(InputDimensions, MaxSimdWidth); +#if defined (USE_AVX512) + static constexpr const IndexType OutputSimdWidth = SimdWidth / 2; +#elif defined (USE_SSSE3) + static constexpr const IndexType OutputSimdWidth = SimdWidth / 4; +#endif // Size of forward propagation buffer used in this layer - static constexpr std::size_t kSelfBufferSize = - CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize); + static constexpr std::size_t SelfBufferSize = + ceil_to_multiple(OutputDimensions * sizeof(OutputType), CacheLineSize); // Size of the forward propagation buffer used from the input layer to this layer - static constexpr std::size_t kBufferSize = - PreviousLayer::kBufferSize + kSelfBufferSize; + static constexpr std::size_t BufferSize = + PreviousLayer::BufferSize + SelfBufferSize; // Hash value embedded in the evaluation file - static constexpr std::uint32_t GetHashValue() { - std::uint32_t hash_value = 0xCC03DAE4u; - hash_value += kOutputDimensions; - hash_value ^= PreviousLayer::GetHashValue() >> 1; - hash_value ^= PreviousLayer::GetHashValue() << 31; - return hash_value; + static constexpr std::uint32_t get_hash_value() { + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= PreviousLayer::get_hash_value() >> 1; + hashValue ^= PreviousLayer::get_hash_value() << 31; + return hashValue; } - // Read network parameters - bool ReadParameters(std::istream& stream) { - if (!previous_layer_.ReadParameters(stream)) return false; - for (std::size_t i = 0; i < kOutputDimensions; ++i) - biases_[i] = read_little_endian(stream); - for (std::size_t i = 0; i < kOutputDimensions * kPaddedInputDimensions; ++i) - weights_[i] = read_little_endian(stream); + // Read network parameters + bool read_parameters(std::istream& stream) { + if (!previousLayer.read_parameters(stream)) return false; + for (std::size_t i = 0; i < OutputDimensions; ++i) + biases[i] = read_little_endian(stream); + for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) +#if !defined (USE_SSSE3) + weights[i] = read_little_endian(stream); +#else + weights[ + (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + + i / PaddedInputDimensions * 4 + + i % 4 + ] = read_little_endian(stream); +#endif + return !stream.fail(); } - // Forward propagation - const OutputType* Propagate( - const TransformedFeatureType* transformed_features, char* buffer) const { - const auto input = previous_layer_.Propagate( - transformed_features, buffer + kSelfBufferSize); - const auto output = reinterpret_cast(buffer); + // Write network parameters + bool write_parameters(std::ostream& stream) const { + if (!previousLayer.write_parameters(stream)) return false; + for (std::size_t i = 0; i < OutputDimensions; ++i) + write_little_endian(stream, biases[i]); +#if !defined (USE_SSSE3) + for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[i]); +#else + std::unique_ptr unscrambledWeights = std::make_unique(OutputDimensions * PaddedInputDimensions); + for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) { + unscrambledWeights[i] = + weights[ + (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + + i / PaddedInputDimensions * 4 + + i % 4 + ]; + } - #if defined(USE_AVX512) - constexpr IndexType kNumChunks = kPaddedInputDimensions / (kSimdWidth * 2); - const auto input_vector = reinterpret_cast(input); - #if !defined(USE_VNNI) - const __m512i kOnes = _mm512_set1_epi16(1); - #endif - - #elif defined(USE_AVX2) - constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; - const auto input_vector = reinterpret_cast(input); - #if !defined(USE_VNNI) - const __m256i kOnes = _mm256_set1_epi16(1); - #endif - - #elif defined(USE_SSE2) - constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; - #ifndef USE_SSSE3 - const __m128i kZeros = _mm_setzero_si128(); - #else - const __m128i kOnes = _mm_set1_epi16(1); - #endif - const auto input_vector = reinterpret_cast(input); - - #elif defined(USE_MMX) - constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; - const __m64 kZeros = _mm_setzero_si64(); - const auto input_vector = reinterpret_cast(input); - - #elif defined(USE_NEON) - constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; - const auto input_vector = reinterpret_cast(input); - #endif - - for (IndexType i = 0; i < kOutputDimensions; ++i) { - const IndexType offset = i * kPaddedInputDimensions; - - #if defined(USE_AVX512) - __m512i sum = _mm512_setzero_si512(); - const auto row = reinterpret_cast(&weights_[offset]); - for (IndexType j = 0; j < kNumChunks; ++j) { - #if defined(USE_VNNI) - sum = _mm512_dpbusd_epi32(sum, _mm512_loadA_si512(&input_vector[j]), _mm512_load_si512(&row[j])); - #else - __m512i product = _mm512_maddubs_epi16(_mm512_loadA_si512(&input_vector[j]), _mm512_load_si512(&row[j])); - product = _mm512_madd_epi16(product, kOnes); - sum = _mm512_add_epi32(sum, product); - #endif - } + for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, unscrambledWeights[i]); +#endif - // Note: Changing kMaxSimdWidth from 32 to 64 breaks loading existing networks. - // As a result kPaddedInputDimensions may not be an even multiple of 64(512bit) - // and we have to do one more 256bit chunk. - if (kPaddedInputDimensions != kNumChunks * kSimdWidth * 2) - { - const auto iv256 = reinterpret_cast(&input_vector[kNumChunks]); - const auto row256 = reinterpret_cast(&row[kNumChunks]); - #if defined(USE_VNNI) - __m256i product256 = _mm256_dpbusd_epi32( - _mm512_castsi512_si256(sum), _mm256_loadA_si256(&iv256[0]), _mm256_load_si256(&row256[0])); - sum = _mm512_inserti32x8(sum, product256, 0); - #else - __m256i product256 = _mm256_maddubs_epi16(_mm256_loadA_si256(&iv256[0]), _mm256_load_si256(&row256[0])); - sum = _mm512_add_epi32(sum, _mm512_cvtepi16_epi32(product256)); - #endif - } - output[i] = _mm512_reduce_add_epi32(sum) + biases_[i]; - - #elif defined(USE_AVX2) - __m256i sum = _mm256_setzero_si256(); - const auto row = reinterpret_cast(&weights_[offset]); - for (IndexType j = 0; j < kNumChunks; ++j) { - #if defined(USE_VNNI) - sum = _mm256_dpbusd_epi32(sum, _mm256_loadA_si256(&input_vector[j]), _mm256_load_si256(&row[j])); - #else - __m256i product = _mm256_maddubs_epi16(_mm256_loadA_si256(&input_vector[j]), _mm256_load_si256(&row[j])); - product = _mm256_madd_epi16(product, kOnes); - sum = _mm256_add_epi32(sum, product); - #endif - } + return !stream.fail(); + } + + // Forward propagation + const OutputType* propagate( + const TransformedFeatureType* transformedFeatures, char* buffer) const { + const auto input = previousLayer.propagate( + transformedFeatures, buffer + SelfBufferSize); + +#if defined (USE_AVX512) + + [[maybe_unused]] const __m512i Ones512 = _mm512_set1_epi16(1); + + [[maybe_unused]] auto m512_hadd = [](__m512i sum, int bias) -> int { + return _mm512_reduce_add_epi32(sum) + bias; + }; + + [[maybe_unused]] auto m512_add_dpbusd_epi32 = [=](__m512i& acc, __m512i a, __m512i b) { +#if defined (USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a, b); +#else + __m512i product0 = _mm512_maddubs_epi16(a, b); + product0 = _mm512_madd_epi16(product0, Ones512); + acc = _mm512_add_epi32(acc, product0); +#endif + }; + + [[maybe_unused]] auto m512_add_dpbusd_epi32x4 = [=](__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1, + __m512i a2, __m512i b2, __m512i a3, __m512i b3) { +#if defined (USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a0, b0); + acc = _mm512_dpbusd_epi32(acc, a1, b1); + acc = _mm512_dpbusd_epi32(acc, a2, b2); + acc = _mm512_dpbusd_epi32(acc, a3, b3); +#else + __m512i product0 = _mm512_maddubs_epi16(a0, b0); + __m512i product1 = _mm512_maddubs_epi16(a1, b1); + __m512i product2 = _mm512_maddubs_epi16(a2, b2); + __m512i product3 = _mm512_maddubs_epi16(a3, b3); + product0 = _mm512_adds_epi16(product0, product1); + product0 = _mm512_madd_epi16(product0, Ones512); + product2 = _mm512_adds_epi16(product2, product3); + product2 = _mm512_madd_epi16(product2, Ones512); + acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product2)); +#endif + }; + +#endif +#if defined (USE_AVX2) + + [[maybe_unused]] const __m256i Ones256 = _mm256_set1_epi16(1); + + [[maybe_unused]] auto m256_hadd = [](__m256i sum, int bias) -> int { __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); - output[i] = _mm_cvtsi128_si32(sum128) + biases_[i]; - - #elif defined(USE_SSSE3) - __m128i sum = _mm_setzero_si128(); - const auto row = reinterpret_cast(&weights_[offset]); - for (int j = 0; j < (int)kNumChunks - 1; j += 2) { - __m128i product0 = _mm_maddubs_epi16(_mm_load_si128(&input_vector[j]), _mm_load_si128(&row[j])); - product0 = _mm_madd_epi16(product0, kOnes); - sum = _mm_add_epi32(sum, product0); - __m128i product1 = _mm_maddubs_epi16(_mm_load_si128(&input_vector[j+1]), _mm_load_si128(&row[j+1])); - product1 = _mm_madd_epi16(product1, kOnes); - sum = _mm_add_epi32(sum, product1); - } - if (kNumChunks & 0x1) { - __m128i product = _mm_maddubs_epi16(_mm_load_si128(&input_vector[kNumChunks-1]), _mm_load_si128(&row[kNumChunks-1])); - product = _mm_madd_epi16(product, kOnes); - sum = _mm_add_epi32(sum, product); - } + return _mm_cvtsi128_si32(sum128) + bias; + }; + + [[maybe_unused]] auto m256_add_dpbusd_epi32 = [=](__m256i& acc, __m256i a, __m256i b) { +#if defined (USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a, b); +#else + __m256i product0 = _mm256_maddubs_epi16(a, b); + product0 = _mm256_madd_epi16(product0, Ones256); + acc = _mm256_add_epi32(acc, product0); +#endif + }; + + [[maybe_unused]] auto m256_add_dpbusd_epi32x4 = [=](__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1, + __m256i a2, __m256i b2, __m256i a3, __m256i b3) { +#if defined (USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a0, b0); + acc = _mm256_dpbusd_epi32(acc, a1, b1); + acc = _mm256_dpbusd_epi32(acc, a2, b2); + acc = _mm256_dpbusd_epi32(acc, a3, b3); +#else + __m256i product0 = _mm256_maddubs_epi16(a0, b0); + __m256i product1 = _mm256_maddubs_epi16(a1, b1); + __m256i product2 = _mm256_maddubs_epi16(a2, b2); + __m256i product3 = _mm256_maddubs_epi16(a3, b3); + product0 = _mm256_adds_epi16(product0, product1); + product0 = _mm256_madd_epi16(product0, Ones256); + product2 = _mm256_adds_epi16(product2, product3); + product2 = _mm256_madd_epi16(product2, Ones256); + acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product2)); +#endif + }; + +#endif +#if defined (USE_SSSE3) + + [[maybe_unused]] const __m128i Ones128 = _mm_set1_epi16(1); + + [[maybe_unused]] auto m128_hadd = [](__m128i sum, int bias) -> int { sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB - output[i] = _mm_cvtsi128_si32(sum) + biases_[i]; + return _mm_cvtsi128_si32(sum) + bias; + }; + + [[maybe_unused]] auto m128_add_dpbusd_epi32 = [=](__m128i& acc, __m128i a, __m128i b) { + __m128i product0 = _mm_maddubs_epi16(a, b); + product0 = _mm_madd_epi16(product0, Ones128); + acc = _mm_add_epi32(acc, product0); + }; + + [[maybe_unused]] auto m128_add_dpbusd_epi32x4 = [=](__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1, + __m128i a2, __m128i b2, __m128i a3, __m128i b3) { + __m128i product0 = _mm_maddubs_epi16(a0, b0); + __m128i product1 = _mm_maddubs_epi16(a1, b1); + __m128i product2 = _mm_maddubs_epi16(a2, b2); + __m128i product3 = _mm_maddubs_epi16(a3, b3); + product0 = _mm_adds_epi16(product0, product1); + product0 = _mm_madd_epi16(product0, Ones128); + product2 = _mm_adds_epi16(product2, product3); + product2 = _mm_madd_epi16(product2, Ones128); + acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product2)); + }; + +#endif + +#if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_setzero _mm512_setzero_si512 + #define vec_set_32 _mm512_set1_epi32 + auto& vec_add_dpbusd_32 = m512_add_dpbusd_epi32; + auto& vec_add_dpbusd_32x4 = m512_add_dpbusd_epi32x4; + auto& vec_hadd = m512_hadd; +#elif defined (USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + auto& vec_add_dpbusd_32 = m256_add_dpbusd_epi32; + auto& vec_add_dpbusd_32x4 = m256_add_dpbusd_epi32x4; + auto& vec_hadd = m256_hadd; +#elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + auto& vec_add_dpbusd_32 = m128_add_dpbusd_epi32; + auto& vec_add_dpbusd_32x4 = m128_add_dpbusd_epi32x4; + auto& vec_hadd = m128_hadd; +#endif + +#if defined (USE_SSSE3) + // Different layout, we process 4 inputs at a time, always. + static_assert(InputDimensions % 4 == 0); - #elif defined(USE_SSE2) - __m128i sum_lo = _mm_cvtsi32_si128(biases_[i]); - __m128i sum_hi = kZeros; - const auto row = reinterpret_cast(&weights_[offset]); - for (IndexType j = 0; j < kNumChunks; ++j) { + const auto output = reinterpret_cast(buffer); + const auto inputVector = reinterpret_cast(input); + + static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1); + + // OutputDimensions is either 1 or a multiple of SimdWidth + // because then it is also an input dimension. + if constexpr (OutputDimensions % OutputSimdWidth == 0) + { + constexpr IndexType NumChunks = InputDimensions / 4; + + const auto input32 = reinterpret_cast(input); + vec_t* outptr = reinterpret_cast(output); + std::memcpy(output, biases, OutputDimensions * sizeof(OutputType)); + + for (int i = 0; i < (int)NumChunks - 3; i += 4) + { + const vec_t in0 = vec_set_32(input32[i + 0]); + const vec_t in1 = vec_set_32(input32[i + 1]); + const vec_t in2 = vec_set_32(input32[i + 2]); + const vec_t in3 = vec_set_32(input32[i + 3]); + const auto col0 = reinterpret_cast(&weights[(i + 0) * OutputDimensions * 4]); + const auto col1 = reinterpret_cast(&weights[(i + 1) * OutputDimensions * 4]); + const auto col2 = reinterpret_cast(&weights[(i + 2) * OutputDimensions * 4]); + const auto col3 = reinterpret_cast(&weights[(i + 3) * OutputDimensions * 4]); + for (int j = 0; j * OutputSimdWidth < OutputDimensions; ++j) + vec_add_dpbusd_32x4(outptr[j], in0, col0[j], in1, col1[j], in2, col2[j], in3, col3[j]); + } + } + else if constexpr (OutputDimensions == 1) + { +#if defined (USE_AVX512) + if constexpr (PaddedInputDimensions % (SimdWidth * 2) != 0) + { + constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; + const auto inputVector256 = reinterpret_cast(input); + + __m256i sum0 = _mm256_setzero_si256(); + const auto row0 = reinterpret_cast(&weights[0]); + + for (int j = 0; j < (int)NumChunks; ++j) + { + const __m256i in = inputVector256[j]; + m256_add_dpbusd_epi32(sum0, in, row0[j]); + } + output[0] = m256_hadd(sum0, biases[0]); + } + else +#endif + { +#if defined (USE_AVX512) + constexpr IndexType NumChunks = PaddedInputDimensions / (SimdWidth * 2); +#else + constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; +#endif + vec_t sum0 = vec_setzero(); + const auto row0 = reinterpret_cast(&weights[0]); + + for (int j = 0; j < (int)NumChunks; ++j) + { + const vec_t in = inputVector[j]; + vec_add_dpbusd_32(sum0, in, row0[j]); + } + output[0] = vec_hadd(sum0, biases[0]); + } + } + +#else + +// Use old implementation for the other architectures. + + auto output = reinterpret_cast(buffer); + +#if defined(USE_SSE2) + // At least a multiple of 16, with SSE2. + static_assert(InputDimensions % SimdWidth == 0); + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m128i Zeros = _mm_setzero_si128(); + const auto inputVector = reinterpret_cast(input); + +#elif defined(USE_MMX) + static_assert(InputDimensions % SimdWidth == 0); + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m64 Zeros = _mm_setzero_si64(); + const auto inputVector = reinterpret_cast(input); + +#elif defined(USE_NEON) + static_assert(InputDimensions % SimdWidth == 0); + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const auto inputVector = reinterpret_cast(input); +#endif + + for (IndexType i = 0; i < OutputDimensions; ++i) { + const IndexType offset = i * PaddedInputDimensions; + +#if defined(USE_SSE2) + __m128i sumLo = _mm_cvtsi32_si128(biases[i]); + __m128i sumHi = Zeros; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) { __m128i row_j = _mm_load_si128(&row[j]); - __m128i input_j = _mm_load_si128(&input_vector[j]); - __m128i row_signs = _mm_cmpgt_epi8(kZeros, row_j); - __m128i extended_row_lo = _mm_unpacklo_epi8(row_j, row_signs); - __m128i extended_row_hi = _mm_unpackhi_epi8(row_j, row_signs); - __m128i extended_input_lo = _mm_unpacklo_epi8(input_j, kZeros); - __m128i extended_input_hi = _mm_unpackhi_epi8(input_j, kZeros); - __m128i product_lo = _mm_madd_epi16(extended_row_lo, extended_input_lo); - __m128i product_hi = _mm_madd_epi16(extended_row_hi, extended_input_hi); - sum_lo = _mm_add_epi32(sum_lo, product_lo); - sum_hi = _mm_add_epi32(sum_hi, product_hi); + __m128i input_j = _mm_load_si128(&inputVector[j]); + __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); + __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); + __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); + __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); + __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); + __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); + sumLo = _mm_add_epi32(sumLo, productLo); + sumHi = _mm_add_epi32(sumHi, productHi); } - __m128i sum = _mm_add_epi32(sum_lo, sum_hi); - __m128i sum_high_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); - sum = _mm_add_epi32(sum, sum_high_64); + __m128i sum = _mm_add_epi32(sumLo, sumHi); + __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sumHigh_64); __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); sum = _mm_add_epi32(sum, sum_second_32); output[i] = _mm_cvtsi128_si32(sum); - #elif defined(USE_MMX) - __m64 sum_lo = _mm_cvtsi32_si64(biases_[i]); - __m64 sum_hi = kZeros; - const auto row = reinterpret_cast(&weights_[offset]); - for (IndexType j = 0; j < kNumChunks; ++j) { +#elif defined(USE_MMX) + __m64 sumLo = _mm_cvtsi32_si64(biases[i]); + __m64 sumHi = Zeros; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) { __m64 row_j = row[j]; - __m64 input_j = input_vector[j]; - __m64 row_signs = _mm_cmpgt_pi8(kZeros, row_j); - __m64 extended_row_lo = _mm_unpacklo_pi8(row_j, row_signs); - __m64 extended_row_hi = _mm_unpackhi_pi8(row_j, row_signs); - __m64 extended_input_lo = _mm_unpacklo_pi8(input_j, kZeros); - __m64 extended_input_hi = _mm_unpackhi_pi8(input_j, kZeros); - __m64 product_lo = _mm_madd_pi16(extended_row_lo, extended_input_lo); - __m64 product_hi = _mm_madd_pi16(extended_row_hi, extended_input_hi); - sum_lo = _mm_add_pi32(sum_lo, product_lo); - sum_hi = _mm_add_pi32(sum_hi, product_hi); + __m64 input_j = inputVector[j]; + __m64 extendedRowLo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8); + __m64 extendedRowHi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8); + __m64 extendedInputLo = _mm_unpacklo_pi8(input_j, Zeros); + __m64 extendedInputHi = _mm_unpackhi_pi8(input_j, Zeros); + __m64 productLo = _mm_madd_pi16(extendedRowLo, extendedInputLo); + __m64 productHi = _mm_madd_pi16(extendedRowHi, extendedInputHi); + sumLo = _mm_add_pi32(sumLo, productLo); + sumHi = _mm_add_pi32(sumHi, productHi); } - __m64 sum = _mm_add_pi32(sum_lo, sum_hi); + __m64 sum = _mm_add_pi32(sumLo, sumHi); sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum)); output[i] = _mm_cvtsi64_si32(sum); - #elif defined(USE_NEON) - int32x4_t sum = {biases_[i]}; - const auto row = reinterpret_cast(&weights_[offset]); - for (IndexType j = 0; j < kNumChunks; ++j) { - int16x8_t product = vmull_s8(input_vector[j * 2], row[j * 2]); - product = vmlal_s8(product, input_vector[j * 2 + 1], row[j * 2 + 1]); +#elif defined(USE_NEON) + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) { + int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); + product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]); sum = vpadalq_s16(sum, product); } output[i] = sum[0] + sum[1] + sum[2] + sum[3]; - #else - OutputType sum = biases_[i]; - for (IndexType j = 0; j < kInputDimensions; ++j) { - sum += weights_[offset + j] * input[j]; +#else + OutputType sum = biases[i]; + for (IndexType j = 0; j < InputDimensions; ++j) { + sum += weights[offset + j] * input[j]; } output[i] = sum; - #endif +#endif } - #if defined(USE_MMX) +#if defined(USE_MMX) _mm_empty(); - #endif +#endif + +#endif + return output; } @@ -254,13 +424,12 @@ namespace Eval::NNUE::Layers { using BiasType = OutputType; using WeightType = std::int8_t; - PreviousLayer previous_layer_; + PreviousLayer previousLayer; - alignas(kCacheLineSize) BiasType biases_[kOutputDimensions]; - alignas(kCacheLineSize) - WeightType weights_[kOutputDimensions * kPaddedInputDimensions]; + alignas(CacheLineSize) BiasType biases[OutputDimensions]; + alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; }; -} // namespace Eval::NNUE::Layers +} // namespace Stockfish::Eval::NNUE::Layers #endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED diff --git a/engines/stockfish/nnue/layers/clipped_relu.h b/engines/stockfish/nnue/layers/clipped_relu.h index 44d8a7d..65455df 100644 --- a/engines/stockfish/nnue/layers/clipped_relu.h +++ b/engines/stockfish/nnue/layers/clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ #include "../nnue_common.h" -namespace Eval::NNUE::Layers { +namespace Stockfish::Eval::NNUE::Layers { // Clipped ReLU template @@ -35,132 +35,157 @@ namespace Eval::NNUE::Layers { static_assert(std::is_same::value, ""); // Number of input/output dimensions - static constexpr IndexType kInputDimensions = - PreviousLayer::kOutputDimensions; - static constexpr IndexType kOutputDimensions = kInputDimensions; + static constexpr IndexType InputDimensions = + PreviousLayer::OutputDimensions; + static constexpr IndexType OutputDimensions = InputDimensions; // Size of forward propagation buffer used in this layer - static constexpr std::size_t kSelfBufferSize = - CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize); + static constexpr std::size_t SelfBufferSize = + ceil_to_multiple(OutputDimensions * sizeof(OutputType), CacheLineSize); // Size of the forward propagation buffer used from the input layer to this layer - static constexpr std::size_t kBufferSize = - PreviousLayer::kBufferSize + kSelfBufferSize; + static constexpr std::size_t BufferSize = + PreviousLayer::BufferSize + SelfBufferSize; // Hash value embedded in the evaluation file - static constexpr std::uint32_t GetHashValue() { - std::uint32_t hash_value = 0x538D24C7u; - hash_value += PreviousLayer::GetHashValue(); - return hash_value; + static constexpr std::uint32_t get_hash_value() { + std::uint32_t hashValue = 0x538D24C7u; + hashValue += PreviousLayer::get_hash_value(); + return hashValue; } // Read network parameters - bool ReadParameters(std::istream& stream) { - return previous_layer_.ReadParameters(stream); + bool read_parameters(std::istream& stream) { + return previousLayer.read_parameters(stream); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + return previousLayer.write_parameters(stream); } // Forward propagation - const OutputType* Propagate( - const TransformedFeatureType* transformed_features, char* buffer) const { - const auto input = previous_layer_.Propagate( - transformed_features, buffer + kSelfBufferSize); + const OutputType* propagate( + const TransformedFeatureType* transformedFeatures, char* buffer) const { + const auto input = previousLayer.propagate( + transformedFeatures, buffer + SelfBufferSize); const auto output = reinterpret_cast(buffer); #if defined(USE_AVX2) - constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth; - const __m256i kZero = _mm256_setzero_si256(); - const __m256i kOffsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m256i*>(output); - for (IndexType i = 0; i < kNumChunks; ++i) { - const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32( - _mm256_loadA_si256(&in[i * 4 + 0]), - _mm256_loadA_si256(&in[i * 4 + 1])), kWeightScaleBits); - const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32( - _mm256_loadA_si256(&in[i * 4 + 2]), - _mm256_loadA_si256(&in[i * 4 + 3])), kWeightScaleBits); - _mm256_storeA_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8( - _mm256_packs_epi16(words0, words1), kZero), kOffsets)); + if constexpr (InputDimensions % SimdWidth == 0) { + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m256i Zero = _mm256_setzero_si256(); + const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m256i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) { + const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32( + _mm256_load_si256(&in[i * 4 + 0]), + _mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits); + const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32( + _mm256_load_si256(&in[i * 4 + 2]), + _mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits); + _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8( + _mm256_packs_epi16(words0, words1), Zero), Offsets)); + } + } else { + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const __m128i Zero = _mm_setzero_si128(); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) { + const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( + _mm_load_si128(&in[i * 4 + 0]), + _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); + const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( + _mm_load_si128(&in[i * 4 + 2]), + _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero)); + } } - constexpr IndexType kStart = kNumChunks * kSimdWidth; + constexpr IndexType Start = + InputDimensions % SimdWidth == 0 + ? InputDimensions / SimdWidth * SimdWidth + : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); #elif defined(USE_SSE2) - constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth; + constexpr IndexType NumChunks = InputDimensions / SimdWidth; #ifdef USE_SSE41 - const __m128i kZero = _mm_setzero_si128(); + const __m128i Zero = _mm_setzero_si128(); #else const __m128i k0x80s = _mm_set1_epi8(-128); #endif const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m128i*>(output); - for (IndexType i = 0; i < kNumChunks; ++i) { + for (IndexType i = 0; i < NumChunks; ++i) { const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( _mm_load_si128(&in[i * 4 + 0]), - _mm_load_si128(&in[i * 4 + 1])), kWeightScaleBits); + _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( _mm_load_si128(&in[i * 4 + 2]), - _mm_load_si128(&in[i * 4 + 3])), kWeightScaleBits); + _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); const __m128i packedbytes = _mm_packs_epi16(words0, words1); _mm_store_si128(&out[i], #ifdef USE_SSE41 - _mm_max_epi8(packedbytes, kZero) + _mm_max_epi8(packedbytes, Zero) #else _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) #endif ); } - constexpr IndexType kStart = kNumChunks * kSimdWidth; + constexpr IndexType Start = NumChunks * SimdWidth; #elif defined(USE_MMX) - constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth; + constexpr IndexType NumChunks = InputDimensions / SimdWidth; const __m64 k0x80s = _mm_set1_pi8(-128); const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m64*>(output); - for (IndexType i = 0; i < kNumChunks; ++i) { + for (IndexType i = 0; i < NumChunks; ++i) { const __m64 words0 = _mm_srai_pi16( _mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]), - kWeightScaleBits); + WeightScaleBits); const __m64 words1 = _mm_srai_pi16( _mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]), - kWeightScaleBits); + WeightScaleBits); const __m64 packedbytes = _mm_packs_pi16(words0, words1); out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); } _mm_empty(); - constexpr IndexType kStart = kNumChunks * kSimdWidth; + constexpr IndexType Start = NumChunks * SimdWidth; #elif defined(USE_NEON) - constexpr IndexType kNumChunks = kInputDimensions / (kSimdWidth / 2); - const int8x8_t kZero = {0}; + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const int8x8_t Zero = {0}; const auto in = reinterpret_cast(input); const auto out = reinterpret_cast(output); - for (IndexType i = 0; i < kNumChunks; ++i) { + for (IndexType i = 0; i < NumChunks; ++i) { int16x8_t shifted; const auto pack = reinterpret_cast(&shifted); - pack[0] = vqshrn_n_s32(in[i * 2 + 0], kWeightScaleBits); - pack[1] = vqshrn_n_s32(in[i * 2 + 1], kWeightScaleBits); - out[i] = vmax_s8(vqmovn_s16(shifted), kZero); + pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); + pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); + out[i] = vmax_s8(vqmovn_s16(shifted), Zero); } - constexpr IndexType kStart = kNumChunks * (kSimdWidth / 2); + constexpr IndexType Start = NumChunks * (SimdWidth / 2); #else - constexpr IndexType kStart = 0; + constexpr IndexType Start = 0; #endif - for (IndexType i = kStart; i < kInputDimensions; ++i) { + for (IndexType i = Start; i < InputDimensions; ++i) { output[i] = static_cast( - std::max(0, std::min(127, input[i] >> kWeightScaleBits))); + std::max(0, std::min(127, input[i] >> WeightScaleBits))); } return output; } private: - PreviousLayer previous_layer_; + PreviousLayer previousLayer; }; -} // namespace Eval::NNUE::Layers +} // namespace Stockfish::Eval::NNUE::Layers #endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED diff --git a/engines/stockfish/nnue/layers/input_slice.h b/engines/stockfish/nnue/layers/input_slice.h index afca14c..b6bf172 100644 --- a/engines/stockfish/nnue/layers/input_slice.h +++ b/engines/stockfish/nnue/layers/input_slice.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,46 +23,51 @@ #include "../nnue_common.h" -namespace Eval::NNUE::Layers { +namespace Stockfish::Eval::NNUE::Layers { // Input layer -template +template class InputSlice { public: // Need to maintain alignment - static_assert(Offset % kMaxSimdWidth == 0, ""); + static_assert(Offset % MaxSimdWidth == 0, ""); // Output type using OutputType = TransformedFeatureType; // Output dimensionality - static constexpr IndexType kOutputDimensions = OutputDimensions; + static constexpr IndexType OutputDimensions = OutDims; // Size of forward propagation buffer used from the input layer to this layer - static constexpr std::size_t kBufferSize = 0; + static constexpr std::size_t BufferSize = 0; // Hash value embedded in the evaluation file - static constexpr std::uint32_t GetHashValue() { - std::uint32_t hash_value = 0xEC42E90Du; - hash_value ^= kOutputDimensions ^ (Offset << 10); - return hash_value; + static constexpr std::uint32_t get_hash_value() { + std::uint32_t hashValue = 0xEC42E90Du; + hashValue ^= OutputDimensions ^ (Offset << 10); + return hashValue; } // Read network parameters - bool ReadParameters(std::istream& /*stream*/) { + bool read_parameters(std::istream& /*stream*/) { + return true; + } + + // Write network parameters + bool write_parameters(std::ostream& /*stream*/) const { return true; } // Forward propagation - const OutputType* Propagate( - const TransformedFeatureType* transformed_features, + const OutputType* propagate( + const TransformedFeatureType* transformedFeatures, char* /*buffer*/) const { - return transformed_features + Offset; + return transformedFeatures + Offset; } private: }; -} // namespace Layers +} // namespace Stockfish::Eval::NNUE::Layers #endif // #ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED diff --git a/engines/stockfish/nnue/nnue_accumulator.h b/engines/stockfish/nnue/nnue_accumulator.h index 2637071..d41ecf9 100644 --- a/engines/stockfish/nnue/nnue_accumulator.h +++ b/engines/stockfish/nnue/nnue_accumulator.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,15 +23,15 @@ #include "nnue_architecture.h" -namespace Eval::NNUE { +namespace Stockfish::Eval::NNUE { // Class that holds the result of affine transformation of input features - struct alignas(kCacheLineSize) Accumulator { - std::int16_t - accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions]; - bool computed_accumulation; + struct alignas(CacheLineSize) Accumulator { + std::int16_t accumulation[2][TransformedFeatureDimensions]; + std::int32_t psqtAccumulation[2][PSQTBuckets]; + bool computed[2]; }; -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE #endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/engines/stockfish/nnue/nnue_architecture.h b/engines/stockfish/nnue/nnue_architecture.h index 91cdc4b..879a39c 100644 --- a/engines/stockfish/nnue/nnue_architecture.h +++ b/engines/stockfish/nnue/nnue_architecture.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,18 +21,40 @@ #ifndef NNUE_ARCHITECTURE_H_INCLUDED #define NNUE_ARCHITECTURE_H_INCLUDED -// Defines the network structure -#include "architectures/halfkp_256x2-32-32.h" +#include "nnue_common.h" -namespace Eval::NNUE { +#include "features/half_ka_v2.h" - static_assert(kTransformedFeatureDimensions % kMaxSimdWidth == 0, ""); - static_assert(Network::kOutputDimensions == 1, ""); - static_assert(std::is_same::value, ""); +#include "layers/input_slice.h" +#include "layers/affine_transform.h" +#include "layers/clipped_relu.h" + +namespace Stockfish::Eval::NNUE { + + // Input features used in evaluation function + using FeatureSet = Features::HalfKAv2; + + // Number of input feature dimensions after conversion + constexpr IndexType TransformedFeatureDimensions = 512; + constexpr IndexType PSQTBuckets = 8; + constexpr IndexType LayerStacks = 8; + + namespace Layers { - // Trigger for full calculation instead of difference calculation - constexpr auto kRefreshTriggers = RawFeatures::kRefreshTriggers; + // Define network structure + using InputLayer = InputSlice; + using HiddenLayer1 = ClippedReLU>; + using HiddenLayer2 = ClippedReLU>; + using OutputLayer = AffineTransform; + + } // namespace Layers + + using Network = Layers::OutputLayer; + + static_assert(TransformedFeatureDimensions % MaxSimdWidth == 0, ""); + static_assert(Network::OutputDimensions == 1, ""); + static_assert(std::is_same::value, ""); -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED diff --git a/engines/stockfish/nnue/nnue_common.h b/engines/stockfish/nnue/nnue_common.h index 7bc905d..75ac786 100644 --- a/engines/stockfish/nnue/nnue_common.h +++ b/engines/stockfish/nnue/nnue_common.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ #include #include +#include "../misc.h" // for IsLittleEndian + #if defined(USE_AVX2) #include @@ -43,77 +45,33 @@ #include #endif -// HACK: Use _mm256_loadu_si256() instead of _mm256_load_si256. Otherwise a binary -// compiled with older g++ crashes because the output memory is not aligned -// even though alignas is specified. -#if defined(USE_AVX2) -#if defined(__GNUC__ ) && (__GNUC__ < 9) && defined(_WIN32) && !defined(__clang__) -#define _mm256_loadA_si256 _mm256_loadu_si256 -#define _mm256_storeA_si256 _mm256_storeu_si256 -#else -#define _mm256_loadA_si256 _mm256_load_si256 -#define _mm256_storeA_si256 _mm256_store_si256 -#endif -#endif - -#if defined(USE_AVX512) -#if defined(__GNUC__ ) && (__GNUC__ < 9) && defined(_WIN32) && !defined(__clang__) -#define _mm512_loadA_si512 _mm512_loadu_si512 -#define _mm512_storeA_si512 _mm512_storeu_si512 -#else -#define _mm512_loadA_si512 _mm512_load_si512 -#define _mm512_storeA_si512 _mm512_store_si512 -#endif -#endif - -namespace Eval::NNUE { +namespace Stockfish::Eval::NNUE { // Version of the evaluation file - constexpr std::uint32_t kVersion = 0x7AF32F16u; + constexpr std::uint32_t Version = 0x7AF32F20u; // Constant used in evaluation value calculation - constexpr int FV_SCALE = 16; - constexpr int kWeightScaleBits = 6; + constexpr int OutputScale = 16; + constexpr int WeightScaleBits = 6; // Size of cache line (in bytes) - constexpr std::size_t kCacheLineSize = 64; + constexpr std::size_t CacheLineSize = 64; // SIMD width (in bytes) #if defined(USE_AVX2) - constexpr std::size_t kSimdWidth = 32; + constexpr std::size_t SimdWidth = 32; #elif defined(USE_SSE2) - constexpr std::size_t kSimdWidth = 16; + constexpr std::size_t SimdWidth = 16; #elif defined(USE_MMX) - constexpr std::size_t kSimdWidth = 8; + constexpr std::size_t SimdWidth = 8; #elif defined(USE_NEON) - constexpr std::size_t kSimdWidth = 16; + constexpr std::size_t SimdWidth = 16; #endif - constexpr std::size_t kMaxSimdWidth = 32; - - // unique number for each piece type on each square - enum { - PS_NONE = 0, - PS_W_PAWN = 1, - PS_B_PAWN = 1 * SQUARE_NB + 1, - PS_W_KNIGHT = 2 * SQUARE_NB + 1, - PS_B_KNIGHT = 3 * SQUARE_NB + 1, - PS_W_BISHOP = 4 * SQUARE_NB + 1, - PS_B_BISHOP = 5 * SQUARE_NB + 1, - PS_W_ROOK = 6 * SQUARE_NB + 1, - PS_B_ROOK = 7 * SQUARE_NB + 1, - PS_W_QUEEN = 8 * SQUARE_NB + 1, - PS_B_QUEEN = 9 * SQUARE_NB + 1, - PS_W_KING = 10 * SQUARE_NB + 1, - PS_END = PS_W_KING, // pieces without kings (pawns included) - PS_B_KING = 11 * SQUARE_NB + 1, - PS_END2 = 12 * SQUARE_NB + 1 - }; - - extern uint32_t kpp_board_index[PIECE_NB][COLOR_NB]; + constexpr std::size_t MaxSimdWidth = 32; // Type of input feature after conversion using TransformedFeatureType = std::uint8_t; @@ -121,7 +79,7 @@ namespace Eval::NNUE { // Round n up to be a multiple of base template - constexpr IntType CeilToMultiple(IntType n, IntType base) { + constexpr IntType ceil_to_multiple(IntType n, IntType base) { return (n + base - 1) / base * base; } @@ -130,19 +88,77 @@ namespace Eval::NNUE { // necessary to return a result with the byte ordering of the compiling machine. template inline IntType read_little_endian(std::istream& stream) { - IntType result; - std::uint8_t u[sizeof(IntType)]; - typename std::make_unsigned::type v = 0; - stream.read(reinterpret_cast(u), sizeof(IntType)); - for (std::size_t i = 0; i < sizeof(IntType); ++i) - v = (v << 8) | u[sizeof(IntType) - i - 1]; + if (IsLittleEndian) + stream.read(reinterpret_cast(&result), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + typename std::make_unsigned::type v = 0; + + stream.read(reinterpret_cast(u), sizeof(IntType)); + for (std::size_t i = 0; i < sizeof(IntType); ++i) + v = (v << 8) | u[sizeof(IntType) - i - 1]; + + std::memcpy(&result, &v, sizeof(IntType)); + } - std::memcpy(&result, &v, sizeof(IntType)); return result; } -} // namespace Eval::NNUE + // write_little_endian() is our utility to write an integer (signed or unsigned, any size) + // to a stream in little-endian order. We swap the byte order before the write if + // necessary to always write in little endian order, independantly of the byte + // ordering of the compiling machine. + template + inline void write_little_endian(std::ostream& stream, IntType value) { + + if (IsLittleEndian) + stream.write(reinterpret_cast(&value), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + typename std::make_unsigned::type v = value; + + std::size_t i = 0; + // if constexpr to silence the warning about shift by 8 + if constexpr (sizeof(IntType) > 1) + { + for (; i + 1 < sizeof(IntType); ++i) + { + u[i] = v; + v >>= 8; + } + } + u[i] = v; + + stream.write(reinterpret_cast(u), sizeof(IntType)); + } + } + + // read_little_endian(s, out, N) : read integers in bulk from a little indian stream. + // This reads N integers from stream s and put them in array out. + template + inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { + if (IsLittleEndian) + stream.read(reinterpret_cast(out), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + out[i] = read_little_endian(stream); + } + + // write_little_endian(s, values, N) : write integers in bulk to a little indian stream. + // This takes N integers from array values and writes them on stream s. + template + inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { + if (IsLittleEndian) + stream.write(reinterpret_cast(values), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + write_little_endian(stream, values[i]); + } + +} // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/engines/stockfish/nnue/nnue_feature_transformer.h b/engines/stockfish/nnue/nnue_feature_transformer.h index e71ee60..59a965a 100644 --- a/engines/stockfish/nnue/nnue_feature_transformer.h +++ b/engines/stockfish/nnue/nnue_feature_transformer.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,72 +23,158 @@ #include "nnue_common.h" #include "nnue_architecture.h" -#include "features/index_list.h" #include // std::memset() -namespace Eval::NNUE { +namespace Stockfish::Eval::NNUE { + + using BiasType = std::int16_t; + using WeightType = std::int16_t; + using PSQTWeightType = std::int32_t; // If vector instructions are enabled, we update and refresh the // accumulator tile by tile such that each tile fits in the CPU's // vector registers. - #define TILING + #define VECTOR + + static_assert(PSQTBuckets % 8 == 0, + "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); #ifdef USE_AVX512 typedef __m512i vec_t; - #define vec_load(a) _mm512_loadA_si512(a) - #define vec_store(a,b) _mm512_storeA_si512(a,b) + typedef __m256i psqt_vec_t; + #define vec_load(a) _mm512_load_si512(a) + #define vec_store(a,b) _mm512_store_si512(a,b) #define vec_add_16(a,b) _mm512_add_epi16(a,b) #define vec_sub_16(a,b) _mm512_sub_epi16(a,b) - static constexpr IndexType kNumRegs = 8; // only 8 are needed + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a,b) _mm256_store_si256(a,b) + #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) + #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 32 #elif USE_AVX2 typedef __m256i vec_t; - #define vec_load(a) _mm256_loadA_si256(a) - #define vec_store(a,b) _mm256_storeA_si256(a,b) + typedef __m256i psqt_vec_t; + #define vec_load(a) _mm256_load_si256(a) + #define vec_store(a,b) _mm256_store_si256(a,b) #define vec_add_16(a,b) _mm256_add_epi16(a,b) #define vec_sub_16(a,b) _mm256_sub_epi16(a,b) - static constexpr IndexType kNumRegs = 16; + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a,b) _mm256_store_si256(a,b) + #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) + #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 16 #elif USE_SSE2 typedef __m128i vec_t; + typedef __m128i psqt_vec_t; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_epi16(a,b) #define vec_sub_16(a,b) _mm_sub_epi16(a,b) - static constexpr IndexType kNumRegs = Is64Bit ? 16 : 8; + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a,b) *(a)=(b) + #define vec_add_psqt_32(a,b) _mm_add_epi32(a,b) + #define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b) + #define vec_zero_psqt() _mm_setzero_si128() + #define NumRegistersSIMD (Is64Bit ? 16 : 8) #elif USE_MMX typedef __m64 vec_t; + typedef __m64 psqt_vec_t; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_pi16(a,b) #define vec_sub_16(a,b) _mm_sub_pi16(a,b) - static constexpr IndexType kNumRegs = 8; + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a,b) *(a)=(b) + #define vec_add_psqt_32(a,b) _mm_add_pi32(a,b) + #define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b) + #define vec_zero_psqt() _mm_setzero_si64() + #define NumRegistersSIMD 8 #elif USE_NEON typedef int16x8_t vec_t; + typedef int32x4_t psqt_vec_t; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) vaddq_s16(a,b) #define vec_sub_16(a,b) vsubq_s16(a,b) - static constexpr IndexType kNumRegs = 16; + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a,b) *(a)=(b) + #define vec_add_psqt_32(a,b) vaddq_s32(a,b) + #define vec_sub_psqt_32(a,b) vsubq_s32(a,b) + #define vec_zero_psqt() psqt_vec_t{0} + #define NumRegistersSIMD 16 #else - #undef TILING + #undef VECTOR + + #endif + + + #ifdef VECTOR + + // Compute optimal SIMD register count for feature transformer accumulation. + + // We use __m* types as template arguments, which causes GCC to emit warnings + // about losing some attribute information. This is irrelevant to us as we + // only take their size, so the following pragma are harmless. + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wignored-attributes" + + template + static constexpr int BestRegisterCount() + { + #define RegisterSize sizeof(SIMDRegisterType) + #define LaneSize sizeof(LaneType) + + static_assert(RegisterSize >= LaneSize); + static_assert(MaxRegisters <= NumRegistersSIMD); + static_assert(MaxRegisters > 0); + static_assert(NumRegistersSIMD > 0); + static_assert(RegisterSize % LaneSize == 0); + static_assert((NumLanes * LaneSize) % RegisterSize == 0); + + const int ideal = (NumLanes * LaneSize) / RegisterSize; + if (ideal <= MaxRegisters) + return ideal; + + // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters + for (int divisor = MaxRegisters; divisor > 1; --divisor) + if (ideal % divisor == 0) + return divisor; + + return 1; + } + + static constexpr int NumRegs = BestRegisterCount(); + static constexpr int NumPsqtRegs = BestRegisterCount(); + + #pragma GCC diagnostic pop #endif + + // Input feature converter class FeatureTransformer { private: // Number of output dimensions for one side - static constexpr IndexType kHalfDimensions = kTransformedFeatureDimensions; + static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; - #ifdef TILING - static constexpr IndexType kTileHeight = kNumRegs * sizeof(vec_t) / 2; - static_assert(kHalfDimensions % kTileHeight == 0, "kTileHeight must divide kHalfDimensions"); + #ifdef VECTOR + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; + static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; + static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); + static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); #endif public: @@ -96,292 +182,434 @@ namespace Eval::NNUE { using OutputType = TransformedFeatureType; // Number of input/output dimensions - static constexpr IndexType kInputDimensions = RawFeatures::kDimensions; - static constexpr IndexType kOutputDimensions = kHalfDimensions * 2; + static constexpr IndexType InputDimensions = FeatureSet::Dimensions; + static constexpr IndexType OutputDimensions = HalfDimensions * 2; // Size of forward propagation buffer - static constexpr std::size_t kBufferSize = - kOutputDimensions * sizeof(OutputType); + static constexpr std::size_t BufferSize = + OutputDimensions * sizeof(OutputType); // Hash value embedded in the evaluation file - static constexpr std::uint32_t GetHashValue() { - - return RawFeatures::kHashValue ^ kOutputDimensions; + static constexpr std::uint32_t get_hash_value() { + return FeatureSet::HashValue ^ OutputDimensions; } // Read network parameters - bool ReadParameters(std::istream& stream) { + bool read_parameters(std::istream& stream) { + + read_little_endian(stream, biases , HalfDimensions ); + read_little_endian(stream, weights , HalfDimensions * InputDimensions); + read_little_endian(stream, psqtWeights, PSQTBuckets * InputDimensions); - for (std::size_t i = 0; i < kHalfDimensions; ++i) - biases_[i] = read_little_endian(stream); - for (std::size_t i = 0; i < kHalfDimensions * kInputDimensions; ++i) - weights_[i] = read_little_endian(stream); return !stream.fail(); } - // Proceed with the difference calculation if possible - bool UpdateAccumulatorIfPossible(const Position& pos) const { + // Write network parameters + bool write_parameters(std::ostream& stream) const { - const auto now = pos.state(); - if (now->accumulator.computed_accumulation) - return true; + write_little_endian(stream, biases , HalfDimensions ); + write_little_endian(stream, weights , HalfDimensions * InputDimensions); + write_little_endian(stream, psqtWeights, PSQTBuckets * InputDimensions); - const auto prev = now->previous; - if (prev && prev->accumulator.computed_accumulation) { - UpdateAccumulator(pos); - return true; - } - - return false; + return !stream.fail(); } // Convert input features - void Transform(const Position& pos, OutputType* output) const { - - if (!UpdateAccumulatorIfPossible(pos)) - RefreshAccumulator(pos); + std::int32_t transform(const Position& pos, OutputType* output, int bucket) const { + update_accumulator(pos, WHITE); + update_accumulator(pos, BLACK); + const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const auto& accumulation = pos.state()->accumulator.accumulation; - - #if defined(USE_AVX2) - constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth; - constexpr int kControl = 0b11011000; - const __m256i kZero = _mm256_setzero_si256(); + const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation; + + const auto psqt = ( + psqtAccumulation[perspectives[0]][bucket] + - psqtAccumulation[perspectives[1]][bucket] + ) / 2; + + + #if defined(USE_AVX512) + + constexpr IndexType NumChunks = HalfDimensions / (SimdWidth * 2); + static_assert(HalfDimensions % (SimdWidth * 2) == 0); + const __m512i Control = _mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7); + const __m512i Zero = _mm512_setzero_si512(); + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + auto out = reinterpret_cast<__m512i*>(&output[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m512i sum0 = _mm512_load_si512(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 0]); + __m512i sum1 = _mm512_load_si512(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 1]); + + _mm512_store_si512(&out[j], _mm512_permutexvar_epi64(Control, + _mm512_max_epi8(_mm512_packs_epi16(sum0, sum1), Zero))); + } + } + return psqt; + + #elif defined(USE_AVX2) + + constexpr IndexType NumChunks = HalfDimensions / SimdWidth; + constexpr int Control = 0b11011000; + const __m256i Zero = _mm256_setzero_si256(); + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + auto out = reinterpret_cast<__m256i*>(&output[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m256i sum0 = _mm256_load_si256(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 0]); + __m256i sum1 = _mm256_load_si256(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 1]); + + _mm256_store_si256(&out[j], _mm256_permute4x64_epi64( + _mm256_max_epi8(_mm256_packs_epi16(sum0, sum1), Zero), Control)); + } + } + return psqt; #elif defined(USE_SSE2) - constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth; - #ifdef USE_SSE41 - const __m128i kZero = _mm_setzero_si128(); - #else + #ifdef USE_SSE41 + constexpr IndexType NumChunks = HalfDimensions / SimdWidth; + const __m128i Zero = _mm_setzero_si128(); + #else + constexpr IndexType NumChunks = HalfDimensions / SimdWidth; const __m128i k0x80s = _mm_set1_epi8(-128); - #endif + #endif + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + auto out = reinterpret_cast<__m128i*>(&output[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m128i sum0 = _mm_load_si128(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 0]); + __m128i sum1 = _mm_load_si128(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 1]); + const __m128i packedbytes = _mm_packs_epi16(sum0, sum1); + + #ifdef USE_SSE41 + _mm_store_si128(&out[j], _mm_max_epi8(packedbytes, Zero)); + #else + _mm_store_si128(&out[j], _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)); + #endif + } + } + return psqt; #elif defined(USE_MMX) - constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth; + + constexpr IndexType NumChunks = HalfDimensions / SimdWidth; const __m64 k0x80s = _mm_set1_pi8(-128); + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + auto out = reinterpret_cast<__m64*>(&output[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m64 sum0 = *(&reinterpret_cast(accumulation[perspectives[p]])[j * 2 + 0]); + __m64 sum1 = *(&reinterpret_cast(accumulation[perspectives[p]])[j * 2 + 1]); + const __m64 packedbytes = _mm_packs_pi16(sum0, sum1); + out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); + } + } + _mm_empty(); + return psqt; + #elif defined(USE_NEON) - constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2); - const int8x8_t kZero = {0}; - #endif - const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - for (IndexType p = 0; p < 2; ++p) { - const IndexType offset = kHalfDimensions * p; - - #if defined(USE_AVX2) - auto out = reinterpret_cast<__m256i*>(&output[offset]); - for (IndexType j = 0; j < kNumChunks; ++j) { - __m256i sum0 = _mm256_loadA_si256( - &reinterpret_cast(accumulation[perspectives[p]][0])[j * 2 + 0]); - __m256i sum1 = _mm256_loadA_si256( - &reinterpret_cast(accumulation[perspectives[p]][0])[j * 2 + 1]); - _mm256_storeA_si256(&out[j], _mm256_permute4x64_epi64(_mm256_max_epi8( - _mm256_packs_epi16(sum0, sum1), kZero), kControl)); - } + constexpr IndexType NumChunks = HalfDimensions / (SimdWidth / 2); + const int8x8_t Zero = {0}; + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + const auto out = reinterpret_cast(&output[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + int16x8_t sum = reinterpret_cast(accumulation[perspectives[p]])[j]; + out[j] = vmax_s8(vqmovn_s16(sum), Zero); + } + } + return psqt; - #elif defined(USE_SSE2) - auto out = reinterpret_cast<__m128i*>(&output[offset]); - for (IndexType j = 0; j < kNumChunks; ++j) { - __m128i sum0 = _mm_load_si128(&reinterpret_cast( - accumulation[perspectives[p]][0])[j * 2 + 0]); - __m128i sum1 = _mm_load_si128(&reinterpret_cast( - accumulation[perspectives[p]][0])[j * 2 + 1]); - const __m128i packedbytes = _mm_packs_epi16(sum0, sum1); - - _mm_store_si128(&out[j], - - #ifdef USE_SSE41 - _mm_max_epi8(packedbytes, kZero) #else - _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + for (IndexType j = 0; j < HalfDimensions; ++j) + { + BiasType sum = accumulation[perspectives[p]][j]; + output[offset + j] = static_cast(std::max(0, std::min(127, sum))); + } + } + return psqt; + #endif - ); - } + } // end of function transform() - #elif defined(USE_MMX) - auto out = reinterpret_cast<__m64*>(&output[offset]); - for (IndexType j = 0; j < kNumChunks; ++j) { - __m64 sum0 = *(&reinterpret_cast( - accumulation[perspectives[p]][0])[j * 2 + 0]); - __m64 sum1 = *(&reinterpret_cast( - accumulation[perspectives[p]][0])[j * 2 + 1]); - const __m64 packedbytes = _mm_packs_pi16(sum0, sum1); - out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); - } - #elif defined(USE_NEON) - const auto out = reinterpret_cast(&output[offset]); - for (IndexType j = 0; j < kNumChunks; ++j) { - int16x8_t sum = reinterpret_cast( - accumulation[perspectives[p]][0])[j]; - out[j] = vmax_s8(vqmovn_s16(sum), kZero); - } - #else - for (IndexType j = 0; j < kHalfDimensions; ++j) { - BiasType sum = accumulation[static_cast(perspectives[p])][0][j]; - output[offset + j] = static_cast( - std::max(0, std::min(127, sum))); - } + private: + void update_accumulator(const Position& pos, const Color perspective) const { + + // The size must be enough to contain the largest possible update. + // That might depend on the feature set and generally relies on the + // feature set's update cost calculation to be correct and never + // allow updates with more added/removed features than MaxActiveDimensions. + using IndexList = ValueList; + + #ifdef VECTOR + // Gcc-10.2 unnecessarily spills AVX2 registers if this array + // is defined in the VECTOR code below, once in each branch + vec_t acc[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; #endif + // Look for a usable accumulator of an earlier position. We keep track + // of the estimated gain in terms of features to be added/subtracted. + StateInfo *st = pos.state(), *next = nullptr; + int gain = FeatureSet::refresh_cost(pos); + while (st->previous && !st->accumulator.computed[perspective]) + { + // This governs when a full feature refresh is needed and how many + // updates are better than just one full refresh. + if ( FeatureSet::requires_refresh(st, perspective) + || (gain -= FeatureSet::update_cost(st) + 1) < 0) + break; + next = st; + st = st->previous; } - #if defined(USE_MMX) - _mm_empty(); - #endif - } - private: - // Calculate cumulative value without using difference calculation - void RefreshAccumulator(const Position& pos) const { - - auto& accumulator = pos.state()->accumulator; - IndexType i = 0; - Features::IndexList active_indices[2]; - RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i], - active_indices); - for (Color perspective : { WHITE, BLACK }) { - #ifdef TILING - for (unsigned j = 0; j < kHalfDimensions / kTileHeight; ++j) { - auto biasesTile = reinterpret_cast( - &biases_[j * kTileHeight]); + if (st->accumulator.computed[perspective]) + { + if (next == nullptr) + return; + + // Update incrementally in two steps. First, we update the "next" + // accumulator. Then, we update the current accumulator (pos.state()). + + // Gather all features to be updated. + const Square ksq = pos.square(perspective); + IndexList removed[2], added[2]; + FeatureSet::append_changed_indices( + ksq, next, perspective, removed[0], added[0]); + for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous) + FeatureSet::append_changed_indices( + ksq, st2, perspective, removed[1], added[1]); + + // Mark the accumulators as computed. + next->accumulator.computed[perspective] = true; + pos.state()->accumulator.computed[perspective] = true; + + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. + StateInfo *states_to_update[3] = + { next, next == pos.state() ? nullptr : pos.state(), nullptr }; + #ifdef VECTOR + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + // Load accumulator auto accTile = reinterpret_cast( - &accumulator.accumulation[perspective][i][j * kTileHeight]); - vec_t acc[kNumRegs]; + &st->accumulator.accumulation[perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTile[k]); - for (unsigned k = 0; k < kNumRegs; ++k) - acc[k] = biasesTile[k]; + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } - for (const auto index : active_indices[perspective]) { - const IndexType offset = kHalfDimensions * index + j * kTileHeight; - auto column = reinterpret_cast(&weights_[offset]); + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } - for (unsigned k = 0; k < kNumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + // Store accumulator + accTile = reinterpret_cast( + &states_to_update[i]->accumulator.accumulation[perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTile[k], acc[k]); } - - for (unsigned k = 0; k < kNumRegs; k++) - vec_store(&accTile[k], acc[k]); } - #else - std::memcpy(accumulator.accumulation[perspective][i], biases_, - kHalfDimensions * sizeof(BiasType)); - for (const auto index : active_indices[perspective]) { - const IndexType offset = kHalfDimensions * index; + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + // Load accumulator + auto accTilePsqt = reinterpret_cast( + &st->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_load_psqt(&accTilePsqt[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } - for (IndexType j = 0; j < kHalfDimensions; ++j) - accumulator.accumulation[perspective][i][j] += weights_[offset + j]; + // Store accumulator + accTilePsqt = reinterpret_cast( + &states_to_update[i]->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } } - #endif - } - #if defined(USE_MMX) - _mm_empty(); - #endif + #else + for (IndexType i = 0; states_to_update[i]; ++i) + { + std::memcpy(states_to_update[i]->accumulator.accumulation[perspective], + st->accumulator.accumulation[perspective], + HalfDimensions * sizeof(BiasType)); - accumulator.computed_accumulation = true; - } + for (std::size_t k = 0; k < PSQTBuckets; ++k) + states_to_update[i]->accumulator.psqtAccumulation[perspective][k] = st->accumulator.psqtAccumulation[perspective][k]; - // Calculate cumulative value using difference calculation - void UpdateAccumulator(const Position& pos) const { + st = states_to_update[i]; - const auto prev_accumulator = pos.state()->previous->accumulator; - auto& accumulator = pos.state()->accumulator; - IndexType i = 0; - Features::IndexList removed_indices[2], added_indices[2]; - bool reset[2]; - RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i], - removed_indices, added_indices, reset); + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index; - #ifdef TILING - for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) { - for (Color perspective : { WHITE, BLACK }) { - auto accTile = reinterpret_cast( - &accumulator.accumulation[perspective][i][j * kTileHeight]); - vec_t acc[kNumRegs]; - - if (reset[perspective]) { - auto biasesTile = reinterpret_cast( - &biases_[j * kTileHeight]); - for (unsigned k = 0; k < kNumRegs; ++k) - acc[k] = biasesTile[k]; - } else { - auto prevAccTile = reinterpret_cast( - &prev_accumulator.accumulation[perspective][i][j * kTileHeight]); - for (IndexType k = 0; k < kNumRegs; ++k) - acc[k] = vec_load(&prevAccTile[k]); + for (IndexType j = 0; j < HalfDimensions; ++j) + st->accumulator.accumulation[perspective][j] -= weights[offset + j]; - // Difference calculation for the deactivated features - for (const auto index : removed_indices[perspective]) { - const IndexType offset = kHalfDimensions * index + j * kTileHeight; - auto column = reinterpret_cast(&weights_[offset]); + for (std::size_t k = 0; k < PSQTBuckets; ++k) + st->accumulator.psqtAccumulation[perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + } - for (IndexType k = 0; k < kNumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + st->accumulator.accumulation[perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + st->accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k]; } - { // Difference calculation for the activated features - for (const auto index : added_indices[perspective]) { - const IndexType offset = kHalfDimensions * index + j * kTileHeight; - auto column = reinterpret_cast(&weights_[offset]); + } + #endif + } + else + { + // Refresh the accumulator + auto& accumulator = pos.state()->accumulator; + accumulator.computed[perspective] = true; + IndexList active; + FeatureSet::append_active_indices(pos, perspective, active); + + #ifdef VECTOR + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + auto biasesTile = reinterpret_cast( + &biases[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasesTile[k]; - for (IndexType k = 0; k < kNumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); } - for (IndexType k = 0; k < kNumRegs; ++k) + auto accTile = reinterpret_cast( + &accumulator.accumulation[perspective][j * TileHeight]); + for (unsigned k = 0; k < NumRegs; k++) vec_store(&accTile[k], acc[k]); } - } - #if defined(USE_MMX) - _mm_empty(); - #endif - #else - for (Color perspective : { WHITE, BLACK }) { - - if (reset[perspective]) { - std::memcpy(accumulator.accumulation[perspective][i], biases_, - kHalfDimensions * sizeof(BiasType)); - } else { - std::memcpy(accumulator.accumulation[perspective][i], - prev_accumulator.accumulation[perspective][i], - kHalfDimensions * sizeof(BiasType)); - // Difference calculation for the deactivated features - for (const auto index : removed_indices[perspective]) { - const IndexType offset = kHalfDimensions * index; + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_zero_psqt(); + + for (const auto index : active) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (IndexType j = 0; j < kHalfDimensions; ++j) - accumulator.accumulation[perspective][i][j] -= weights_[offset + j]; + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); } + + auto accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); } - { // Difference calculation for the activated features - for (const auto index : added_indices[perspective]) { - const IndexType offset = kHalfDimensions * index; - for (IndexType j = 0; j < kHalfDimensions; ++j) - accumulator.accumulation[perspective][i][j] += weights_[offset + j]; - } + #else + std::memcpy(accumulator.accumulation[perspective], biases, + HalfDimensions * sizeof(BiasType)); + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[perspective][k] = 0; + + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + accumulator.accumulation[perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k]; } - } #endif + } - accumulator.computed_accumulation = true; + #if defined(USE_MMX) + _mm_empty(); + #endif } - using BiasType = std::int16_t; - using WeightType = std::int16_t; - - alignas(kCacheLineSize) BiasType biases_[kHalfDimensions]; - alignas(kCacheLineSize) - WeightType weights_[kHalfDimensions * kInputDimensions]; + alignas(CacheLineSize) BiasType biases[HalfDimensions]; + alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; + alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; }; -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED diff --git a/engines/stockfish/pawns.cpp b/engines/stockfish/pawns.cpp index af0f661..70fb6f2 100644 --- a/engines/stockfish/pawns.cpp +++ b/engines/stockfish/pawns.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,35 +24,38 @@ #include "position.h" #include "thread.h" +namespace Stockfish { + namespace { #define V Value #define S(mg, eg) make_score(mg, eg) // Pawn penalties - constexpr Score Backward = S( 8, 27); - constexpr Score Doubled = S(11, 55); - constexpr Score Isolated = S( 5, 17); - constexpr Score WeakLever = S( 2, 54); - constexpr Score WeakUnopposed = S(15, 25); + constexpr Score Backward = S( 9, 22); + constexpr Score Doubled = S(13, 51); + constexpr Score DoubledEarly = S(20, 7); + constexpr Score Isolated = S( 3, 15); + constexpr Score WeakLever = S( 4, 58); + constexpr Score WeakUnopposed = S(13, 24); // Bonus for blocked pawns at 5th or 6th rank - constexpr Score BlockedPawn[2] = { S(-13, -4), S(-4, 3) }; + constexpr Score BlockedPawn[2] = { S(-17, -6), S(-9, 2) }; constexpr Score BlockedStorm[RANK_NB] = { - S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2) + S(0, 0), S(0, 0), S(75, 78), S(-8, 16), S(-6, 10), S(-6, 6), S(0, 2) }; // Connected pawn bonus - constexpr int Connected[RANK_NB] = { 0, 7, 8, 11, 24, 45, 85 }; + constexpr int Connected[RANK_NB] = { 0, 5, 7, 11, 23, 48, 87 }; // Strength of pawn shelter for our king by [distance from edge][rank]. // RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king. constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = { - { V( -6), V( 81), V( 93), V( 58), V( 39), V( 18), V( 25) }, - { V(-43), V( 61), V( 35), V(-49), V(-29), V(-11), V( -63) }, - { V(-10), V( 75), V( 23), V( -2), V( 32), V( 3), V( -45) }, - { V(-39), V(-13), V(-29), V(-52), V(-48), V(-67), V(-166) } + { V( -5), V( 82), V( 92), V( 54), V( 36), V( 22), V( 28) }, + { V(-44), V( 63), V( 33), V(-50), V(-30), V(-12), V( -62) }, + { V(-11), V( 77), V( 22), V( -6), V( 31), V( 8), V( -45) }, + { V(-39), V(-12), V(-29), V(-50), V(-43), V(-68), V(-164) } }; // Danger of enemy pawns moving toward our king by [distance from edge][rank]. @@ -60,12 +63,18 @@ namespace { // is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn // on edge, likely blocked by our king. constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = { - { V( 85), V(-289), V(-166), V(97), V(50), V( 45), V( 50) }, - { V( 46), V( -25), V( 122), V(45), V(37), V(-10), V( 20) }, - { V( -6), V( 51), V( 168), V(34), V(-2), V(-22), V(-14) }, - { V(-15), V( -11), V( 101), V( 4), V(11), V(-15), V(-29) } + { V( 87), V(-288), V(-168), V( 96), V( 47), V( 44), V( 46) }, + { V( 42), V( -25), V( 120), V( 45), V( 34), V( -9), V( 24) }, + { V( -8), V( 51), V( 167), V( 35), V( -4), V(-16), V(-12) }, + { V(-17), V( -13), V( 100), V( 4), V( 9), V(-16), V(-31) } }; + + // KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties + // for king when the king is on a semi-open or open file. + constexpr Score KingOnFile[2][2] = {{ S(-21,10), S(-7, 1) }, + { S( 0,-3), S( 9,-4) }}; + #undef S #undef V @@ -80,13 +89,14 @@ namespace { constexpr Color Them = ~Us; constexpr Direction Up = pawn_push(Us); + constexpr Direction Down = -Up; Bitboard neighbours, stoppers, support, phalanx, opposed; Bitboard lever, leverPush, blocked; Square s; bool backward, passed, doubled; Score score = SCORE_ZERO; - const Square* pl = pos.squares(Us); + Bitboard b = pos.pieces(Us, PAWN); Bitboard ourPawns = pos.pieces( Us, PAWN); Bitboard theirPawns = pos.pieces(Them, PAWN); @@ -99,8 +109,10 @@ namespace { e->blockedCount += popcount(shift(ourPawns) & (theirPawns | doubleAttackThem)); // Loop through all pawns of the current color and score each pawn - while ((s = *pl++) != SQ_NONE) + while (b) { + s = pop_lsb(b); + assert(pos.piece_on(s) == make_piece(Us, PAWN)); Rank r = relative_rank(Us, s); @@ -116,6 +128,13 @@ namespace { phalanx = neighbours & rank_bb(s); support = neighbours & rank_bb(s - Up); + if (doubled) + { + // Additional doubled penalty if none of their pawns is fixed + if (!(ourPawns & shift(theirPawns | pawn_attacks_bb(theirPawns)))) + score -= DoubledEarly; + } + // A pawn is backward when it is behind all pawns of the same color on // the adjacent files and cannot safely advance. backward = !(neighbours & forward_ranks_bb(Them, s + Up)) @@ -147,7 +166,7 @@ namespace { if (support | phalanx) { int v = Connected[r] * (2 + bool(phalanx) - bool(opposed)) - + 21 * popcount(support); + + 22 * popcount(support); score += make_score(v, v * (r - 2) / 4); } @@ -165,14 +184,14 @@ namespace { else if (backward) score -= Backward - + WeakUnopposed * !opposed; + + WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s); if (!support) score -= Doubled * doubled + WeakLever * more_than_one(lever); - if (blocked && r > RANK_4) - score += BlockedPawn[r-4]; + if (blocked && r >= RANK_5) + score += BlockedPawn[r - RANK_5]; } return score; @@ -237,6 +256,9 @@ Score Entry::evaluate_shelter(const Position& pos, Square ksq) const { bonus -= make_score(UnblockedStorm[d][theirRank], 0); } + // King On File + bonus -= KingOnFile[pos.is_on_semiopen_file(Us, ksq)][pos.is_on_semiopen_file(Them, ksq)]; + return bonus; } @@ -269,7 +291,7 @@ Score Entry::do_king_safety(const Position& pos) { if (pawns & attacks_bb(ksq)) minPawnDist = 1; else while (pawns) - minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns))); + minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(pawns))); return shelter - make_score(0, 16 * minPawnDist); } @@ -279,3 +301,5 @@ template Score Entry::do_king_safety(const Position& pos); template Score Entry::do_king_safety(const Position& pos); } // namespace Pawns + +} // namespace Stockfish diff --git a/engines/stockfish/pawns.h b/engines/stockfish/pawns.h index 5499826..124619d 100644 --- a/engines/stockfish/pawns.h +++ b/engines/stockfish/pawns.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ #include "position.h" #include "types.h" -namespace Pawns { +namespace Stockfish::Pawns { /// Pawns::Entry contains various information about a pawn structure. A lookup /// to the pawn hash table (performed by calling the probe function) returns a @@ -65,6 +65,6 @@ typedef HashTable Table; Entry* probe(const Position& pos); -} // namespace Pawns +} // namespace Stockfish::Pawns #endif // #ifndef PAWNS_H_INCLUDED diff --git a/engines/stockfish/position.cpp b/engines/stockfish/position.cpp index e6a760d..0686d24 100644 --- a/engines/stockfish/position.cpp +++ b/engines/stockfish/position.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,6 +34,8 @@ using std::string; +namespace Stockfish { + namespace Zobrist { Key psq[PIECE_NB][SQUARE_NB]; @@ -71,12 +73,14 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { << std::setfill(' ') << std::dec << "\nCheckers: "; for (Bitboard b = pos.checkers(); b; ) - os << UCI::square(pop_lsb(&b)) << " "; + os << UCI::square(pop_lsb(b)) << " "; if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + Position p; p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); Tablebases::ProbeState s1, s2; @@ -195,7 +199,6 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th std::memset(this, 0, sizeof(Position)); std::memset(si, 0, sizeof(StateInfo)); - std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE); st = si; ss >> std::noskipws; @@ -302,7 +305,7 @@ void Position::set_castling_right(Color c, Square rfrom) { Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); - castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto) + castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom); } @@ -341,7 +344,7 @@ void Position::set_state(StateInfo* si) const { for (Bitboard b = pieces(); b; ) { - Square s = pop_lsb(&b); + Square s = pop_lsb(b); Piece pc = piece_on(s); si->key ^= Zobrist::psq[pc][s]; @@ -392,7 +395,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { /// Position::fen() returns a FEN representation of the position. In case of /// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. -const string Position::fen() const { +string Position::fen() const { int emptyCnt; std::ostringstream ss; @@ -458,7 +461,7 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners while (snipers) { - Square sniperSq = pop_lsb(&snipers); + Square sniperSq = pop_lsb(snipers); Bitboard b = between_bb(s, sniperSq) & occupancy; if (b && !more_than_one(b)) @@ -502,7 +505,7 @@ bool Position::legal(Move m) const { // En passant captures are a tricky special case. Because they are rather // uncommon, we do it simply by testing whether the king is attacked after // the move is made. - if (type_of(m) == ENPASSANT) + if (type_of(m) == EN_PASSANT) { Square ksq = square(us); Square capsq = to - pawn_push(us); @@ -530,22 +533,20 @@ bool Position::legal(Move m) const { if (attackers_to(s) & pieces(~us)) return false; - // In case of Chess960, verify that when moving the castling rook we do - // not discover some hidden checker. + // In case of Chess960, verify if the Rook blocks some checks // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - return !chess960 - || !(attacks_bb(to, pieces() ^ to_sq(m)) & pieces(~us, ROOK, QUEEN)); + return !chess960 || !(blockers_for_king(us) & to_sq(m)); } // If the moving piece is a king, check whether the destination square is // attacked by the opponent. if (type_of(piece_on(from)) == KING) - return !(attackers_to(to) & pieces(~us)); + return !(attackers_to(to, pieces() ^ from) & pieces(~us)); // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) - || aligned(from, to, square(us)); + return !(blockers_for_king(us) & from) + || aligned(from, to, square(us)); } @@ -561,8 +562,10 @@ bool Position::pseudo_legal(const Move m) const { Piece pc = moved_piece(m); // Use a slower but simpler function for uncommon cases + // yet we skip the legality check of MoveList(). if (type_of(m) != NORMAL) - return MoveList(*this).contains(m); + return checkers() ? MoveList< EVASIONS>(*this).contains(m) + : MoveList(*this).contains(m); // Is not a promotion, so promotion piece must be empty if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE) @@ -607,8 +610,8 @@ bool Position::pseudo_legal(const Move m) const { if (more_than_one(checkers())) return false; - // Our move must be a blocking evasion or a capture of the checking piece - if (!((between_bb(lsb(checkers()), square(us)) | checkers()) & to)) + // Our move must be a blocking interposition or a capture of the checking piece + if (!(between_bb(square(us), lsb(checkers())) & to)) return false; } // In case of king moves under check we have to remove king so as to catch @@ -652,7 +655,7 @@ bool Position::gives_check(Move m) const { // of direct checks and ordinary discovered check, so the only case we // need to handle is the unusual case of a discovered check through // the captured pawn. - case ENPASSANT: + case EN_PASSANT: { Square capsq = make_square(file_of(to), rank_of(from)); Bitboard b = (pieces() ^ from ^ capsq) | to; @@ -660,19 +663,15 @@ bool Position::gives_check(Move m) const { return (attacks_bb< ROOK>(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) | (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP)); } - case CASTLING: + default: //CASTLING { - Square kfrom = from; - Square rfrom = to; // Castling is encoded as 'king captures the rook' - Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1); - Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1); + // Castling is encoded as 'king captures the rook' + Square ksq = square(~sideToMove); + Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); - return (attacks_bb(rto) & square(~sideToMove)) - && (attacks_bb(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square(~sideToMove)); + return (attacks_bb(rto) & ksq) + && (attacks_bb(rto, pieces() ^ from ^ to) & ksq); } - default: - assert(false); - return false; } } @@ -703,7 +702,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ++st->pliesFromNull; // Used by NNUE - st->accumulator.computed_accumulation = false; + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; auto& dp = st->dirtyPiece; dp.dirty_num = 1; @@ -712,7 +712,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Square from = from_sq(m); Square to = to_sq(m); Piece pc = piece_on(from); - Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to); + Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); assert(color_of(pc) == us); assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); @@ -738,7 +738,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // update non-pawn material. if (type_of(captured) == PAWN) { - if (type_of(m) == ENPASSANT) + if (type_of(m) == EN_PASSANT) { capsq -= pawn_push(us); @@ -765,7 +765,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update board and piece lists remove_piece(capsq); - if (type_of(m) == ENPASSANT) + if (type_of(m) == EN_PASSANT) board[capsq] = NO_PIECE; // Update material hash key and prefetch access to materialTable @@ -811,7 +811,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // If the moving piece is a pawn do some special extra work if (type_of(pc) == PAWN) { - // Set en-passant square if the moved pawn can be captured + // Set en passant square if the moved pawn can be captured if ( (int(to) ^ int(from)) == 16 && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) { @@ -934,7 +934,7 @@ void Position::undo_move(Move m) { { Square capsq = to; - if (type_of(m) == ENPASSANT) + if (type_of(m) == EN_PASSANT) { capsq -= pawn_push(us); @@ -988,7 +988,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ } -/// Position::do(undo)_null_move() is used to do(undo) a "null move": it flips +/// Position::do_null_move() is used to do a "null move": it flips /// the side to move without executing any move on the board. void Position::do_null_move(StateInfo& newSt) { @@ -996,16 +996,16 @@ void Position::do_null_move(StateInfo& newSt) { assert(!checkers()); assert(&newSt != st); - if (Eval::useNNUE) - { - std::memcpy(&newSt, st, sizeof(StateInfo)); - } - else - std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); + std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); newSt.previous = st; st = &newSt; + st->dirtyPiece.dirty_num = 0; + st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + if (st->epSquare != SQ_NONE) { st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; @@ -1013,7 +1013,7 @@ void Position::do_null_move(StateInfo& newSt) { } st->key ^= Zobrist::side; - prefetch(TT.first_entry(st->key)); + prefetch(TT.first_entry(key())); ++st->rule50; st->pliesFromNull = 0; @@ -1027,6 +1027,9 @@ void Position::do_null_move(StateInfo& newSt) { assert(pos_is_ok()); } + +/// Position::undo_null_move() must be used to undo a "null move" + void Position::undo_null_move() { assert(!checkers()); @@ -1038,7 +1041,7 @@ void Position::undo_null_move() { /// Position::key_after() computes the new hash key after the given move. Needed /// for speculative prefetch. It doesn't recognize special moves like castling, -/// en-passant and promotions. +/// en passant and promotions. Key Position::key_after(Move m) const { @@ -1063,7 +1066,7 @@ bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); - // Only deal with normal moves, assume others pass a simple see + // Only deal with normal moves, assume others pass a simple SEE if (type_of(m) != NORMAL) return VALUE_ZERO >= threshold; @@ -1077,8 +1080,9 @@ bool Position::see_ge(Move m, Value threshold) const { if (swap <= 0) return true; + assert(color_of(piece_on(from)) == sideToMove); Bitboard occupied = pieces() ^ from ^ to; - Color stm = color_of(piece_on(from)); + Color stm = sideToMove; Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; int res = 1; @@ -1092,8 +1096,8 @@ bool Position::see_ge(Move m, Value threshold) const { if (!(stmAttackers = attackers & pieces(stm))) break; - // Don't allow pinned pieces to attack (except the king) as long as - // there are pinners on their original square. + // Don't allow pinned pieces to attack as long as there are + // pinners on their original square. if (pinners(~stm) & occupied) stmAttackers &= ~blockers_for_king(stm); @@ -1109,7 +1113,7 @@ bool Position::see_ge(Move m, Value threshold) const { if ((swap = PawnValueMg - swap) < res) break; - occupied ^= lsb(bb); + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } @@ -1118,7 +1122,7 @@ bool Position::see_ge(Move m, Value threshold) const { if ((swap = KnightValueMg - swap) < res) break; - occupied ^= lsb(bb); + occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) @@ -1126,7 +1130,7 @@ bool Position::see_ge(Move m, Value threshold) const { if ((swap = BishopValueMg - swap) < res) break; - occupied ^= lsb(bb); + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } @@ -1135,7 +1139,7 @@ bool Position::see_ge(Move m, Value threshold) const { if ((swap = RookValueMg - swap) < res) break; - occupied ^= lsb(bb); + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } @@ -1144,7 +1148,7 @@ bool Position::see_ge(Move m, Value threshold) const { if ((swap = QueenValueMg - swap) < res) break; - occupied ^= lsb(bb); + occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); } @@ -1218,7 +1222,7 @@ bool Position::has_game_cycle(int ply) const { Square s1 = from_sq(move); Square s2 = to_sq(move); - if (!(between_bb(s1, s2) & pieces())) + if (!((between_bb(s1, s2) ^ s2) & pieces())) { if (ply > i) return true; @@ -1315,21 +1319,17 @@ bool Position::pos_is_ok() const { assert(0 && "pos_is_ok: Bitboards"); StateInfo si = *st; + ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize); + set_state(&si); if (std::memcmp(&si, st, sizeof(StateInfo))) assert(0 && "pos_is_ok: State"); for (Piece pc : Pieces) - { if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) assert(0 && "pos_is_ok: Pieces"); - for (int i = 0; i < pieceCount[pc]; ++i) - if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i) - assert(0 && "pos_is_ok: Index"); - } - for (Color c : { WHITE, BLACK }) for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) { @@ -1344,3 +1344,5 @@ bool Position::pos_is_ok() const { return true; } + +} // namespace Stockfish diff --git a/engines/stockfish/position.h b/engines/stockfish/position.h index d6f5c9f..9f694a7 100644 --- a/engines/stockfish/position.h +++ b/engines/stockfish/position.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,10 +26,12 @@ #include "bitboard.h" #include "evaluate.h" +#include "psqt.h" #include "types.h" #include "nnue/nnue_accumulator.h" +namespace Stockfish { /// StateInfo struct stores information needed to restore a Position object to /// its previous state when we retract a move. Whenever a move is made on the @@ -49,11 +51,11 @@ struct StateInfo { // Not copied when making a move (will be recomputed anyhow) Key key; Bitboard checkersBB; - Piece capturedPiece; StateInfo* previous; Bitboard blockersForKing[COLOR_NB]; Bitboard pinners[COLOR_NB]; Bitboard checkSquares[PIECE_TYPE_NB]; + Piece capturedPiece; int repetition; // Used by NNUE @@ -86,7 +88,7 @@ class Position { // FEN string input/output Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); Position& set(const std::string& code, Color c, StateInfo* si); - const std::string fen() const; + std::string fen() const; // Position representation Bitboard pieces(PieceType pt) const; @@ -99,7 +101,6 @@ class Position { bool empty(Square s) const; template int count(Color c) const; template int count() const; - template const Square* squares(Color c) const; template Square square(Color c) const; bool is_on_semiopen_file(Color c, Square s) const; @@ -114,7 +115,6 @@ class Position { Bitboard blockers_for_king(Color c) const; Bitboard check_squares(PieceType pt) const; Bitboard pinners(Color c) const; - bool is_discovery_check_on_king(Color c, Move m) const; // Attacks to/from a given square Bitboard attackers_to(Square s) const; @@ -127,7 +127,6 @@ class Position { bool capture(Move m) const; bool capture_or_promotion(Move m) const; bool gives_check(Move m) const; - bool advanced_pawn_push(Move m) const; Piece moved_piece(Move m) const; Piece captured_piece() const; @@ -172,6 +171,9 @@ class Position { // Used by NNUE StateInfo* state() const; + void put_piece(Piece pc, Square s); + void remove_piece(Square s); + private: // Initialization helpers (used while setting up a position) void set_castling_right(Color c, Square rfrom); @@ -179,8 +181,6 @@ class Position { void set_check_info(StateInfo* si) const; // Other helpers - void put_piece(Piece pc, Square s); - void remove_piece(Square s); void move_piece(Square from, Square to); template void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); @@ -190,23 +190,17 @@ class Position { Bitboard byTypeBB[PIECE_TYPE_NB]; Bitboard byColorBB[COLOR_NB]; int pieceCount[PIECE_NB]; - Square pieceList[PIECE_NB][16]; - int index[SQUARE_NB]; int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB]; + Thread* thisThread; + StateInfo* st; int gamePly; Color sideToMove; Score psq; - Thread* thisThread; - StateInfo* st; bool chess960; }; -namespace PSQT { - extern Score psq[PIECE_NB][SQUARE_NB]; -} - extern std::ostream& operator<<(std::ostream& os, const Position& pos); inline Color Position::side_to_move() const { @@ -254,13 +248,9 @@ template inline int Position::count() const { return count(WHITE) + count(BLACK); } -template inline const Square* Position::squares(Color c) const { - return pieceList[make_piece(c, Pt)]; -} - template inline Square Position::square(Color c) const { - assert(pieceCount[make_piece(c, Pt)] == 1); - return squares(c)[0]; + assert(count(c) == 1); + return lsb(pieces(c, Pt)); } inline Square Position::ep_square() const { @@ -311,25 +301,17 @@ inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; } -inline bool Position::is_discovery_check_on_king(Color c, Move m) const { - return st->blockersForKing[c] & from_sq(m); -} - inline bool Position::pawn_passed(Color c, Square s) const { return !(pieces(~c, PAWN) & passed_pawn_span(c, s)); } -inline bool Position::advanced_pawn_push(Move m) const { - return type_of(moved_piece(m)) == PAWN - && relative_rank(sideToMove, to_sq(m)) > RANK_5; -} - inline int Position::pawns_on_same_color_squares(Color c, Square s) const { return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares)); } inline Key Position::key() const { - return st->key; + return st->rule50 < 14 ? st->key + : st->key ^ make_key((st->rule50 - 14) / 8); } inline Key Position::pawn_key() const { @@ -378,7 +360,7 @@ inline bool Position::capture_or_promotion(Move m) const { inline bool Position::capture(Move m) const { assert(is_ok(m)); // Castling is encoded as "king captures rook" - return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == ENPASSANT; + return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT; } inline Piece Position::captured_piece() const { @@ -394,35 +376,25 @@ inline void Position::put_piece(Piece pc, Square s) { board[s] = pc; byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; byColorBB[color_of(pc)] |= s; - index[s] = pieceCount[pc]++; - pieceList[pc][index[s]] = s; + pieceCount[pc]++; pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; psq += PSQT::psq[pc][s]; } inline void Position::remove_piece(Square s) { - // WARNING: This is not a reversible operation. If we remove a piece in - // do_move() and then replace it in undo_move() we will put it at the end of - // the list and not in its original place, it means index[] and pieceList[] - // are not invariant to a do_move() + undo_move() sequence. Piece pc = board[s]; byTypeBB[ALL_PIECES] ^= s; byTypeBB[type_of(pc)] ^= s; byColorBB[color_of(pc)] ^= s; - /* board[s] = NO_PIECE; Not needed, overwritten by the capturing one */ - Square lastSquare = pieceList[pc][--pieceCount[pc]]; - index[lastSquare] = index[s]; - pieceList[pc][index[lastSquare]] = lastSquare; - pieceList[pc][pieceCount[pc]] = SQ_NONE; + board[s] = NO_PIECE; + pieceCount[pc]--; pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; psq -= PSQT::psq[pc][s]; } inline void Position::move_piece(Square from, Square to) { - // index[from] is not updated and becomes stale. This works as long as index[] - // is accessed just by known occupied squares. Piece pc = board[from]; Bitboard fromTo = from | to; byTypeBB[ALL_PIECES] ^= fromTo; @@ -430,8 +402,6 @@ inline void Position::move_piece(Square from, Square to) { byColorBB[color_of(pc)] ^= fromTo; board[from] = NO_PIECE; board[to] = pc; - index[to] = index[from]; - pieceList[pc][index[to]] = to; psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; } @@ -444,4 +414,6 @@ inline StateInfo* Position::state() const { return st; } +} // namespace Stockfish + #endif // #ifndef POSITION_H_INCLUDED diff --git a/engines/stockfish/psqt.cpp b/engines/stockfish/psqt.cpp index eb36e75..33a3e00 100644 --- a/engines/stockfish/psqt.cpp +++ b/engines/stockfish/psqt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,19 +16,23 @@ along with this program. If not, see . */ + +#include "psqt.h" + #include -#include "types.h" #include "bitboard.h" +#include "types.h" + +namespace Stockfish { -namespace PSQT { +namespace +{ -#define S(mg, eg) make_score(mg, eg) +auto constexpr S = make_score; -// Bonus[PieceType][Square / 2] contains Piece-Square scores. For each piece -// type on a given square a (middlegame, endgame) score pair is assigned. Table -// is defined for files A..D and white side: it is symmetric for black side and -// second half of the files. +// 'Bonus' contains Piece-Square parameters. +// Scores are explicit for files A to D, implicitly mirrored for E to H. constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { { }, { }, @@ -43,14 +47,14 @@ constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { { S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) } }, { // Bishop - { S(-53,-57), S( -5,-30), S( -8,-37), S(-23,-12) }, - { S(-15,-37), S( 8,-13), S( 19,-17), S( 4, 1) }, - { S( -7,-16), S( 21, -1), S( -5, -2), S( 17, 10) }, - { S( -5,-20), S( 11, -6), S( 25, 0), S( 39, 17) }, - { S(-12,-17), S( 29, -1), S( 22,-14), S( 31, 15) }, - { S(-16,-30), S( 6, 6), S( 1, 4), S( 11, 6) }, - { S(-17,-31), S(-14,-20), S( 5, -1), S( 0, 1) }, - { S(-48,-46), S( 1,-42), S(-14,-37), S(-23,-24) } + { S(-37,-40), S(-4 ,-21), S( -6,-26), S(-16, -8) }, + { S(-11,-26), S( 6, -9), S( 13,-12), S( 3, 1) }, + { S(-5 ,-11), S( 15, -1), S( -4, -1), S( 12, 7) }, + { S(-4 ,-14), S( 8, -4), S( 18, 0), S( 27, 12) }, + { S(-8 ,-12), S( 20, -1), S( 15,-10), S( 22, 11) }, + { S(-11,-21), S( 4, 4), S( 1, 3), S( 8, 4) }, + { S(-12,-22), S(-10,-14), S( 4, -1), S( 0, 1) }, + { S(-34,-32), S( 1,-29), S(-10,-26), S(-16,-17) } }, { // Rook { S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) }, @@ -64,13 +68,13 @@ constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { }, { // Queen { S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) }, - { S(-3,-55), S( 5,-31), S( 8,-22), S(12, -4) }, + { S(-3,-54), S( 5,-31), S( 8,-22), S(12, -4) }, { S(-3,-39), S( 6,-18), S(13, -9), S( 7, 3) }, { S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) }, { S( 0,-29), S(14, -6), S(12, 9), S( 5, 21) }, - { S(-4,-38), S(10,-18), S( 6,-12), S( 8, 1) }, + { S(-4,-38), S(10,-18), S( 6,-11), S( 8, 1) }, { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) }, - { S(-2,-75), S(-2,-52), S( 1,-43), S(-2,-36) } + { S(-2,-74), S(-2,-52), S( 1,-43), S(-2,-34) } }, { // King { S(271, 1), S(327, 45), S(271, 85), S(198, 76) }, @@ -87,18 +91,21 @@ constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { constexpr Score PBonus[RANK_NB][FILE_NB] = { // Pawn (asymmetric distribution) { }, - { S( 3,-10), S( 3, -6), S( 10, 10), S( 19, 0), S( 16, 14), S( 19, 7), S( 7, -5), S( -5,-19) }, - { S( -9,-10), S(-15,-10), S( 11,-10), S( 15, 4), S( 32, 4), S( 22, 3), S( 5, -6), S(-22, -4) }, - { S( -4, 6), S(-23, -2), S( 6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S( 4,-10), S( -8, -9) }, - { S( 13, 10), S( 0, 5), S(-13, 4), S( 1, -5), S( 11, -5), S( -2, -5), S(-13, 14), S( 5, 9) }, - { S( 5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5, 7), S(-15, 6), S( -8, 13) }, - { S( -7, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) } + { S( 2, -8), S( 4, -6), S( 11, 9), S( 18, 5), S( 16, 16), S( 21, 6), S( 9, -6), S( -3,-18) }, + { S( -9, -9), S(-15, -7), S( 11,-10), S( 15, 5), S( 31, 2), S( 23, 3), S( 6, -8), S(-20, -5) }, + { S( -3, 7), S(-20, 1), S( 8, -8), S( 19, -2), S( 39,-14), S( 17,-13), S( 2,-11), S( -5, -6) }, + { S( 11, 12), S( -4, 6), S(-11, 2), S( 2, -6), S( 11, -5), S( 0, -4), S(-12, 14), S( 5, 9) }, + { S( 3, 27), S(-11, 18), S( -6, 19), S( 22, 29), S( -8, 30), S( -5, 9), S(-14, 8), S(-11, 14) }, + { S( -7, -1), S( 6,-14), S( -2, 13), S(-11, 22), S( 4, 24), S(-14, 17), S( 10, 7), S( -9, 7) } }; -#undef S +} // namespace -Score psq[PIECE_NB][SQUARE_NB]; +namespace PSQT +{ + +Score psq[PIECE_NB][SQUARE_NB]; // PSQT::init() initializes piece-square tables: the white halves of the tables are // copied from Bonus[] and PBonus[], adding the piece value, then the black halves of @@ -107,16 +114,18 @@ void init() { for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING}) { - Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); - - for (Square s = SQ_A1; s <= SQ_H8; ++s) - { - File f = File(edge_distance(file_of(s))); - psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)] - : Bonus[pc][rank_of(s)][f]); - psq[~pc][flip_rank(s)] = -psq[pc][s]; - } + Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); + + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + File f = File(edge_distance(file_of(s))); + psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)] + : Bonus[pc][rank_of(s)][f]); + psq[~pc][flip_rank(s)] = -psq[pc][s]; + } } } } // namespace PSQT + +} // namespace Stockfish diff --git a/engines/stockfish/psqt.h b/engines/stockfish/psqt.h new file mode 100644 index 0000000..7abb148 --- /dev/null +++ b/engines/stockfish/psqt.h @@ -0,0 +1,38 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#ifndef PSQT_H_INCLUDED +#define PSQT_H_INCLUDED + + +#include "types.h" + + +namespace Stockfish::PSQT +{ + +extern Score psq[PIECE_NB][SQUARE_NB]; + +// Fill psqt array from a set of internally linked parameters +extern void init(); + +} // namespace Stockfish::PSQT + + +#endif // PSQT_H_INCLUDED diff --git a/engines/stockfish/search.cpp b/engines/stockfish/search.cpp index 000785a..030307e 100644 --- a/engines/stockfish/search.cpp +++ b/engines/stockfish/search.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,9 +35,12 @@ #include "uci.h" #include "syzygy/tbprobe.h" +// Added for BanksiaGUI #include "engineids.h" void engine_message(int eid, const std::string& s); +namespace Stockfish { + namespace Search { LimitsType Limits; @@ -57,19 +60,17 @@ using std::string; using Eval::evaluate; using namespace Search; - namespace { // Different node types, used as a template parameter - enum NodeType { NonPV, PV }; + enum NodeType { NonPV, PV, Root }; constexpr uint64_t TtHitAverageWindow = 4096; constexpr uint64_t TtHitAverageResolution = 1024; - // Razor and futility margins - constexpr int RazorMargin = 510; + // Futility margin Value futility_margin(Depth d, bool improving) { - return Value(223 * (d - improving)); + return Value(214 * (d - improving)); } // Reductions lookup table, initialized at startup @@ -77,7 +78,7 @@ namespace { Depth reduction(bool i, Depth d, int mn) { int r = Reductions[d] * Reductions[mn]; - return (r + 509) / 1024 + (!i && r > 894); + return (r + 534) / 1024 + (!i && r > 904); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -86,10 +87,10 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return d > 13 ? 29 : 17 * d * d + 134 * d - 134; + return d > 14 ? 73 : 6 * d * d + 229 * d - 215; } - // Add a small random component to draw evaluations to avoid 3fold-blindness + // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(Thread* thisThread) { return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1); } @@ -105,53 +106,10 @@ namespace { Move best = MOVE_NONE; }; - // Breadcrumbs are used to mark nodes as being searched by a given thread - struct Breadcrumb { - std::atomic thread; - std::atomic key; - }; - std::array breadcrumbs; - - // ThreadHolding structure keeps track of which thread left breadcrumbs at the given - // node for potential reductions. A free node will be marked upon entering the moves - // loop by the constructor, and unmarked upon leaving that loop by the destructor. - struct ThreadHolding { - explicit ThreadHolding(Thread* thisThread, Key posKey, int ply) { - location = ply < 8 ? &breadcrumbs[posKey & (breadcrumbs.size() - 1)] : nullptr; - otherThread = false; - owning = false; - if (location) - { - // See if another already marked this location, if not, mark it ourselves - Thread* tmp = (*location).thread.load(std::memory_order_relaxed); - if (tmp == nullptr) - { - (*location).thread.store(thisThread, std::memory_order_relaxed); - (*location).key.store(posKey, std::memory_order_relaxed); - owning = true; - } - else if ( tmp != thisThread - && (*location).key.load(std::memory_order_relaxed) == posKey) - otherThread = true; - } - } - - ~ThreadHolding() { - if (owning) // Free the marked location - (*location).thread.store(nullptr, std::memory_order_relaxed); - } - - bool marked() { return otherThread; } - - private: - Breadcrumb* location; - bool otherThread, owning; - }; - - template + template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); - template + template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); Value value_to_tt(Value v, int ply); @@ -165,9 +123,11 @@ namespace { // perft() is our utility to verify move generation. All the leaf nodes up // to the given depth are generated and counted, and the sum is returned. template - uint64_t sf_perft(Position& pos, Depth depth) { + uint64_t perft(Position& pos, Depth depth) { StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + uint64_t cnt, nodes = 0; const bool leaf = (depth == 2); @@ -178,14 +138,15 @@ namespace { else { pos.do_move(m, st); - cnt = leaf ? MoveList(pos).size() : sf_perft(pos, depth - 1); + cnt = leaf ? MoveList(pos).size() : perft(pos, depth - 1); nodes += cnt; pos.undo_move(m); } - if (Root) { + if (Root) { // sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; - engine_message(stockfish, UCI::move(m, pos.is_chess960()) + ": " + std::to_string(cnt)); - } + // Added for BanksiaGUI + engine_message(stockfish, UCI::move(m, pos.is_chess960()) + ": " + std::to_string(cnt)); + } } return nodes; } @@ -198,7 +159,7 @@ namespace { void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((22.0 + std::log(Threads.size())) * std::log(i)); + Reductions[i] = int(21.9 * std::log(i)); } @@ -222,9 +183,10 @@ void MainThread::search() { if (Limits.perft) { - nodes = sf_perft(rootPos, Limits.perft); + nodes = perft(rootPos, Limits.perft); // sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; - engine_message(stockfish, "\nNodes searched: " + std::to_string(nodes)); + // Added for BanksiaGUI + engine_message(stockfish, "\nNodes searched: " + std::to_string(nodes)); return; } @@ -232,7 +194,7 @@ void MainThread::search() { Time.init(Limits, us, rootPos.game_ply()); TT.new_search(); - Eval::verify_NNUE(); + Eval::NNUE::verify(); if (rootMoves.empty()) { @@ -240,8 +202,9 @@ void MainThread::search() { // sync_cout << "info depth 0 score " // << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) // << sync_endl; - engine_message(stockfish, "info depth 0 score " + UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW)); + // Added for BanksiaGUI + engine_message(stockfish, "info depth 0 score " + UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW)); } else { @@ -280,25 +243,27 @@ void MainThread::search() { bestPreviousScore = bestThread->rootMoves[0].score; - // Send again PV info if we have a new best thread - if (bestThread != this) { +// // Send again PV info if we have a new best thread +// if (bestThread != this) // sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl; - engine_message(stockfish, UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE)); - - } - +// // sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); // // if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) // std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); // // std::cout << sync_endl; - - auto s = "bestmove " + UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); - if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) - s += " ponder " + UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); - engine_message(stockfish, s); + + // Added for BanksiaGUI + // Send again PV info if we have a new best thread + if (bestThread != this) { + engine_message(stockfish, UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE)); + } + auto s = "bestmove " + UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) + s += " ponder " + UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + engine_message(stockfish, s); } @@ -311,7 +276,7 @@ void Thread::search() { // To allow access to (ss-7) up to (ss+2), the stack must be oversized. // The former is needed to allow update_continuation_histories(ss-1, ...), // which accesses its argument at ss-6, also near the root. - // The latter is needed for statScores and killer initialization. + // The latter is needed for statScore and killer initialization. Stack stack[MAX_PLY+10], *ss = stack+7; Move pv[MAX_PLY+1]; Value bestValue, alpha, beta, delta; @@ -326,6 +291,9 @@ void Thread::search() { for (int i = 7; i > 0; i--) (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel + for (int i = 0; i <= MAX_PLY + 2; ++i) + (ss+i)->ply = i; + ss->pv = pv; bestValue = delta = alpha = -VALUE_INFINITE; @@ -367,19 +335,7 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); ttHitAverage = TtHitAverageWindow * TtHitAverageResolution / 2; - int ct = int(Options["Contempt"]) * PawnValueEg / 100; // From centipawns - - // In analysis mode, adjust contempt in accordance with user preference - if (Limits.infinite || Options["UCI_AnalyseMode"]) - ct = Options["Analysis Contempt"] == "Off" ? 0 - : Options["Analysis Contempt"] == "Both" ? ct - : Options["Analysis Contempt"] == "White" && us == BLACK ? -ct - : Options["Analysis Contempt"] == "Black" && us == WHITE ? -ct - : ct; - - // Evaluation score is from the white point of view - contempt = (us == WHITE ? make_score(ct, ct / 2) - : -make_score(ct, ct / 2)); + trend = SCORE_ZERO; int searchAgainCounter = 0; @@ -425,11 +381,11 @@ void Thread::search() { alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); - // Adjust contempt based on root move's previousScore (dynamic contempt) - int dct = ct + (105 - ct / 2) * prev / (abs(prev) + 149); + // Adjust trend based on root move's previousScore (dynamic contempt) + int tr = 113 * prev / (abs(prev) + 147); - contempt = (us == WHITE ? make_score(dct, dct / 2) - : -make_score(dct, dct / 2)); + trend = (us == WHITE ? make_score(tr, tr / 2) + : -make_score(tr, tr / 2)); } // Start with a small aspiration window and, in the case of a fail @@ -439,7 +395,7 @@ void Thread::search() { while (true) { Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter); - bestValue = ::search(rootPos, ss, alpha, beta, adjustedDepth, false); + bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the @@ -462,8 +418,9 @@ void Thread::search() { && (bestValue <= alpha || bestValue >= beta) && Time.elapsed() > 3000) { // sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; - engine_message(stockfish, UCI::pv(rootPos, rootDepth, alpha, beta)); - + + // Added for BanksiaGUI + engine_message(stockfish, UCI::pv(rootPos, rootDepth, alpha, beta)); } // In case of failing low/high increase aspiration window and @@ -483,10 +440,7 @@ void Thread::search() { ++failedHighCnt; } else - { - ++rootMoves[pvIdx].bestMoveCount; break; - } delta += delta / 4 + 5; @@ -499,7 +453,8 @@ void Thread::search() { if ( mainThread && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) { // sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; - engine_message(stockfish, UCI::pv(rootPos, rootDepth, alpha, beta)); + // Added for BanksiaGUI + engine_message(stockfish, UCI::pv(rootPos, rootDepth, alpha, beta)); } } @@ -543,12 +498,16 @@ void Thread::search() { totBestMoveChanges += th->bestMoveChanges; th->bestMoveChanges = 0; } - double bestMoveInstability = 1 + 2 * totBestMoveChanges / Threads.size(); + double bestMoveInstability = 1.073 + std::max(1.0, 2.25 - 9.9 / rootDepth) + * totBestMoveChanges / Threads.size(); + double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; - double totalTime = rootMoves.size() == 1 ? 0 : - Time.optimum() * fallingEval * reduction * bestMoveInstability; + // Cap used time in case of a single legal move for a better viewer experience in tournaments + // yielding correct scores and sufficiently fast moves. + if (rootMoves.size() == 1) + totalTime = std::min(500.0, totalTime); - // Stop the search if we have exceeded the totalTime, at least 1ms search + // Stop the search if we have exceeded the totalTime if (Time.elapsed() > totalTime) { // If we are allowed to ponder do not stop the search now but @@ -586,17 +545,18 @@ namespace { // search<>() is the main search function for both PV and non-PV nodes - template + template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { - constexpr bool PvNode = NT == PV; - const bool rootNode = PvNode && ss->ply == 0; + constexpr bool PvNode = nodeType != NonPV; + constexpr bool rootNode = nodeType == Root; + const Depth maxNextDepth = rootNode ? depth : depth + 1; // Check if we have an upcoming move which draws by repetition, or // if the opponent had an alternative move earlier to this position. - if ( pos.rule50_count() >= 3 + if ( !rootNode + && pos.rule50_count() >= 3 && alpha < VALUE_DRAW - && !rootNode && pos.has_game_cycle(ss->ply)) { alpha = value_draw(pos.this_thread()); @@ -606,7 +566,7 @@ namespace { // Dive into quiescence search when the depth reaches zero if (depth <= 0) - return qsearch(pos, ss, alpha, beta); + return qsearch(pos, ss, alpha, beta); assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); @@ -615,12 +575,14 @@ namespace { Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + TTEntry* tte; Key posKey; Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool formerPv, givesCheck, improving, didLMR, priorCapture; + bool givesCheck, improving, didLMR, priorCapture; bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture, singularQuietLMR; Piece movedPiece; @@ -628,14 +590,12 @@ namespace { // Step 1. Initialize node Thread* thisThread = pos.this_thread(); - ss->inCheck = pos.checkers(); - priorCapture = pos.captured_piece(); - Color us = pos.side_to_move(); - moveCount = captureCount = quietCount = ss->moveCount = 0; - bestValue = -VALUE_INFINITE; - maxValue = VALUE_INFINITE; - - Eval::evalHits = Eval::nnueHits = 0; + ss->inCheck = pos.checkers(); + priorCapture = pos.captured_piece(); + Color us = pos.side_to_move(); + moveCount = captureCount = quietCount = ss->moveCount = 0; + bestValue = -VALUE_INFINITE; + maxValue = VALUE_INFINITE; // Check for the available remaining time if (thisThread == Threads.main()) @@ -668,11 +628,11 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss+1)->ply = ss->ply + 1; - (ss+1)->ttPv = false; + (ss+1)->ttPv = false; (ss+1)->excludedMove = bestMove = MOVE_NONE; - (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; - Square prevSq = to_sq((ss-1)->currentMove); + (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + ss->doubleExtensions = (ss-1)->doubleExtensions; + Square prevSq = to_sq((ss-1)->currentMove); // Initialize statScore to zero for the grandchildren of the current position. // So statScore is shared between all grandchildren and only the first grandchild @@ -693,8 +653,8 @@ namespace { : ss->ttHit ? tte->move() : MOVE_NONE; if (!excludedMove) ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); - formerPv = ss->ttPv && !PvNode; + // Update low ply history for previous move if we are near root and position is or has been in PV if ( ss->ttPv && depth > 12 && ss->ply - 1 < MAX_LPH @@ -719,6 +679,7 @@ namespace { { if (ttValue >= beta) { + // Bonus for a quiet ttMove that fails high if (!pos.capture_or_promotion(ttMove)) update_quiet_stats(pos, ss, ttMove, stat_bonus(depth), depth); @@ -735,6 +696,8 @@ namespace { } } + // Partial workaround for the graph history interaction problem + // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) return ttValue; } @@ -808,6 +771,7 @@ namespace { if (eval == VALUE_NONE) ss->staticEval = eval = evaluate(pos); + // Randomize draw evaluation if (eval == VALUE_DRAW) eval = value_draw(thisThread); @@ -818,38 +782,46 @@ namespace { } else { + // In case of null move search use previous static eval with a different sign + // and addition of two tempos if ((ss-1)->currentMove != MOVE_NULL) ss->staticEval = eval = evaluate(pos); else - ss->staticEval = eval = -(ss-1)->staticEval + 2 * Tempo; + ss->staticEval = eval = -(ss-1)->staticEval; + // Save static evaluation into transposition table + if(!excludedMove) tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } - // Step 7. Razoring (~1 Elo) - if ( !rootNode // The required rootNode PV handling is not available in qsearch - && depth == 1 - && eval <= alpha - RazorMargin) - return qsearch(pos, ss, alpha, beta); + // Use static evaluation difference to improve quiet move ordering + if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) + { + int bonus = std::clamp(-depth * 4 * int((ss-1)->staticEval + ss->staticEval), -1000, 1000); + thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; + } + // Set up improving flag that is used in various pruning heuristics + // We define position as improving if static evaluation of position is better + // Than the previous static evaluation at our turn + // In case of us being in check at our previous move we look at move prior to it improving = (ss-2)->staticEval == VALUE_NONE ? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE : ss->staticEval > (ss-2)->staticEval; - // Step 8. Futility pruning: child node (~50 Elo) + // Step 7. Futility pruning: child node (~50 Elo) if ( !PvNode - && depth < 8 && eval - futility_margin(depth, improving) >= beta && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; - // Step 9. Null move search with verification search (~40 Elo) + // Step 8. Null move search with verification search (~40 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 22977 + && (ss-1)->statScore < 23767 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 30 * depth - 28 * improving + 84 * ss->ttPv + 182 + && ss->staticEval >= beta - 20 * depth - 22 * improving + 168 * ss->ttPv + 159 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -857,7 +829,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and value - Depth R = (817 + 71 * depth) / 213 + std::min(int(eval - beta) / 192, 3); + Depth R = (1090 + 81 * depth) / 256 + std::min(int(eval - beta) / 205, 3); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -874,7 +846,7 @@ namespace { if (nullValue >= VALUE_TB_WIN_IN_MAX_PLY) nullValue = beta; - if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 13)) + if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 14)) return nullValue; assert(!thisThread->nmpMinPly); // Recursive verification is not allowed @@ -893,9 +865,9 @@ namespace { } } - probCutBeta = beta + 176 - 49 * improving; + probCutBeta = beta + 209 - 44 * improving; - // Step 10. ProbCut (~10 Elo) + // Step 9. ProbCut (~4 Elo) // If we have a good enough capture and a reduced search returns a value // much above beta, we can (almost) safely prune the previous move. if ( !PvNode @@ -910,17 +882,8 @@ namespace { && ttValue != VALUE_NONE && ttValue < probCutBeta)) { - // if ttMove is a capture and value from transposition table is good enough produce probCut - // cutoff without digging into actual probCut search - if ( ss->ttHit - && tte->depth() >= depth - 3 - && ttValue != VALUE_NONE - && ttValue >= probCutBeta - && ttMove - && pos.capture_or_promotion(ttMove)) - return probCutBeta; - assert(probCutBeta < VALUE_INFINITE); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); int probCutCount = 0; bool ttPv = ss->ttPv; @@ -968,7 +931,7 @@ namespace { ss->ttPv = ttPv; } - // Step 11. If the position is not in TT, decrease depth by 2 + // Step 10. If the position is not in TT, decrease depth by 2 if ( PvNode && depth >= 6 && !ttMove) @@ -976,6 +939,23 @@ namespace { moves_loop: // When in check, search starts from here + ttCapture = ttMove && pos.capture_or_promotion(ttMove); + + // Step 11. A small Probcut idea, when we are in check + probCutBeta = beta + 409; + if ( ss->inCheck + && !PvNode + && depth >= 4 + && ttCapture + && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 3 + && ttValue >= probCutBeta + && abs(ttValue) <= VALUE_KNOWN_WIN + && abs(beta) <= VALUE_KNOWN_WIN + ) + return probCutBeta; + + const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, nullptr , (ss-4)->continuationHistory, nullptr , (ss-6)->continuationHistory }; @@ -992,10 +972,14 @@ namespace { value = bestValue; singularQuietLMR = moveCountPruning = false; - ttCapture = ttMove && pos.capture_or_promotion(ttMove); + bool doubleExtension = false; - // Mark this node as being searched - ThreadHolding th(thisThread, posKey, ss->ply); + // Indicate PvNodes that will probably fail low if the node was searched + // at a depth equal or greater than the current depth, and the result of this search was a fail low. + bool likelyFailLow = PvNode + && ttMove + && (tte->bound() & BOUND_UPPER) + && tte->depth() >= depth; // Step 12. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -1020,19 +1004,17 @@ namespace { ss->moveCount = ++moveCount; - if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) { + if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) { // sync_cout << "info depth " << depth // << " currmove " << UCI::move(move, pos.is_chess960()) // << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; - auto s = "info depth " + std::to_string(depth) + - " currmove " + UCI::move(move, pos.is_chess960()) + - " currmovenumber " + std::to_string(moveCount + thisThread->pvIdx); - if (Eval::nnueHits && Eval::evalHits) { - s += " nnuehits " + std::to_string(static_cast(Eval::nnueHits * 1000 / Eval::evalHits)); + + // Added for BanksiaGUI + auto s = "info depth " + std::to_string(depth) + + " currmove " + UCI::move(move, pos.is_chess960()) + + " currmovenumber " + std::to_string(moveCount + thisThread->pvIdx); + engine_message(stockfish, s); } - engine_message(stockfish, s); - } - if (PvNode) (ss+1)->pv = nullptr; @@ -1055,48 +1037,38 @@ namespace { // Reduced depth of the next LMR search int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0); - if ( !captureOrPromotion - && !givesCheck) + if ( captureOrPromotion + || givesCheck) { - // Countermoves based pruning (~20 Elo) - if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1) + // Capture history based pruning when the move doesn't give check + if ( !givesCheck + && lmrDepth < 1 + && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0) + continue; + + // SEE based pruning + if (!pos.see_ge(move, Value(-218) * depth)) // (~25 Elo) + continue; + } + else + { + // Continuation history based pruning (~20 Elo) + if ( lmrDepth < 5 && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold) continue; // Futility pruning: parent node (~5 Elo) - if ( lmrDepth < 7 - && !ss->inCheck - && ss->staticEval + 283 + 170 * lmrDepth <= alpha + if ( !ss->inCheck + && ss->staticEval + 174 + 157 * lmrDepth <= alpha && (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - + (*contHist[5])[movedPiece][to_sq(move)] / 2 < 27376) + + (*contHist[5])[movedPiece][to_sq(move)] / 3 < 28255) continue; // Prune moves with negative SEE (~20 Elo) - if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth))) - continue; - } - else - { - // Capture history based pruning when the move doesn't give check - if ( !givesCheck - && lmrDepth < 1 - && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0) - continue; - - // Futility pruning for captures - if ( !givesCheck - && lmrDepth < 6 - && !(PvNode && abs(bestValue) < 2) - && !ss->inCheck - && ss->staticEval + 169 + 244 * lmrDepth - + PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] <= alpha) - continue; - - // See based pruning - if (!pos.see_ge(move, Value(-221) * depth)) // (~25 Elo) + if (!pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth))) continue; } } @@ -1108,17 +1080,18 @@ namespace { // then that move is singular and should be extended. To verify this we do // a reduced search on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin, then we will extend the ttMove. - if ( depth >= 7 + if ( !rootNode + && depth >= 7 && move == ttMove - && !rootNode && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ && abs(ttValue) < VALUE_KNOWN_WIN && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - ((formerPv + 4) * depth) / 2; - Depth singularDepth = (depth - 1 + 3 * formerPv) / 2; + Value singularBeta = ttValue - 2 * depth; + Depth singularDepth = (depth - 1) / 2; + ss->excludedMove = move; value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); ss->excludedMove = MOVE_NONE; @@ -1127,6 +1100,15 @@ namespace { { extension = 1; singularQuietLMR = !ttCapture; + + // Avoid search explosion by limiting the number of double extensions to at most 3 + if ( !PvNode + && value < singularBeta - 93 + && ss->doubleExtensions < 3) + { + extension = 2; + doubleExtension = true; + } } // Multi-cut pruning @@ -1149,25 +1131,14 @@ namespace { return beta; } } - - // Check extension (~2 Elo) - else if ( givesCheck - && (pos.is_discovery_check_on_king(~us, move) || pos.see_ge(move))) + else if ( givesCheck + && depth > 6 + && abs(ss->staticEval) > Value(100)) extension = 1; - // Last captures extension - else if ( PieceValue[EG][pos.captured_piece()] > PawnValueEg - && pos.non_pawn_material() <= 2 * RookValueMg) - extension = 1; - - // Late irreversible move extension - if ( move == ttMove - && pos.rule50_count() > 80 - && (captureOrPromotion || type_of(movedPiece) == PAWN)) - extension = 2; - // Add extension to new depth newDepth += extension; + ss->doubleExtensions = (ss-1)->doubleExtensions + (extension == 2); // Speculative prefetch as early as possible prefetch(TT.first_entry(pos.key_after(move))); @@ -1182,98 +1153,80 @@ namespace { // Step 15. Make the move pos.do_move(move, st, givesCheck); - // Step 16. Reduced depth search (LMR, ~200 Elo). If the move fails high it will be - // re-searched at full depth. + // Step 16. Late moves reduction / extension (LMR, ~200 Elo) + // We use various heuristics for the sons of a node after the first son has + // been searched. In general we would like to reduce them, but there are many + // cases where we extend a son if it has good chances to be "interesting". if ( depth >= 3 && moveCount > 1 + 2 * rootNode && ( !captureOrPromotion - || moveCountPruning - || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha - || cutNode - || thisThread->ttHitAverage < 427 * TtHitAverageResolution * TtHitAverageWindow / 1024)) + || (cutNode && (ss-1)->moveCount > 1) + || !ss->ttPv) + && (!PvNode || ss->ply > 1 || thisThread->id() % 4 != 3)) { Depth r = reduction(improving, depth, moveCount); - // Decrease reduction if the ttHit running average is large - if (thisThread->ttHitAverage > 509 * TtHitAverageResolution * TtHitAverageWindow / 1024) + if (PvNode) r--; - // Reduction if other threads are searching this position - if (th.marked()) - r++; + // Decrease reduction if the ttHit running average is large (~0 Elo) + if (thisThread->ttHitAverage > 537 * TtHitAverageResolution * TtHitAverageWindow / 1024) + r--; - // Decrease reduction if position is or has been on the PV (~10 Elo) - if (ss->ttPv) + // Decrease reduction if position is or has been on the PV + // and node is not likely to fail low. (~3 Elo) + if ( ss->ttPv + && !likelyFailLow) r -= 2; - if (moveCountPruning && !formerPv) + // Increase reduction at root and non-PV nodes when the best move does not change frequently + if ( (rootNode || !PvNode) + && thisThread->bestMoveChanges <= 2) r++; - // Decrease reduction if opponent's move count is high (~5 Elo) + // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss-1)->moveCount > 13) r--; - // Decrease reduction if ttMove has been singularly extended (~3 Elo) + // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) r--; + // Increase reduction for cut nodes (~3 Elo) + if (cutNode && move != ss->killers[0]) + r += 2; + if (!captureOrPromotion) { - // Increase reduction if ttMove is a capture (~5 Elo) + // Increase reduction if ttMove is a capture (~3 Elo) if (ttCapture) r++; - // Increase reduction for cut nodes (~10 Elo) - if (cutNode) - r += 2; - - // Decrease reduction for moves that escape a capture. Filter out - // castling moves, because they are coded as "king captures rook" and - // hence break make_move(). (~2 Elo) - else if ( type_of(move) == NORMAL - && !pos.see_ge(reverse_move(move))) - r -= 2 + ss->ttPv - (type_of(movedPiece) == PAWN); - ss->statScore = thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 5287; - - // Decrease/increase reduction by comparing opponent's stat score (~10 Elo) - if (ss->statScore >= -106 && (ss-1)->statScore < -104) - r--; - - else if ((ss-1)->statScore >= -119 && ss->statScore < -140) - r++; + - 4923; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / 14884; - } - else - { - // Increase reduction for captures/promotions if late move and at low depth - if (depth < 8 && moveCount > 2) - r++; - - // Unless giving check, this capture is likely bad - if ( !givesCheck - && ss->staticEval + PieceValue[EG][pos.captured_piece()] + 213 * depth <= alpha) - r++; + if (!ss->inCheck) + r -= ss->statScore / 14721; } - Depth d = std::clamp(newDepth - r, 1, newDepth); + // In general we want to cap the LMR depth search at newDepth. But if + // reductions are really negative and movecount is low, we allow this move + // to be searched deeper than the first move, unless ttMove was extended by 2. + Depth d = std::clamp(newDepth - r, 1, newDepth + (r < -1 && moveCount <= 5 && !doubleExtension)); value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); - doFullDepthSearch = value > alpha && d != newDepth; - + // If the son is reduced and fails high it will be re-searched at full depth + doFullDepthSearch = value > alpha && d < newDepth; didLMR = true; } else { doFullDepthSearch = !PvNode || moveCount > 1; - didLMR = false; } @@ -1282,14 +1235,12 @@ namespace { { value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); + // If the move passed LMR update its stats if (didLMR && !captureOrPromotion) { int bonus = value > alpha ? stat_bonus(newDepth) : -stat_bonus(newDepth); - if (move == ss->killers[0]) - bonus += bonus / 4; - update_continuation_histories(ss, movedPiece, to_sq(move), bonus); } } @@ -1302,7 +1253,8 @@ namespace { (ss+1)->pv = pv; (ss+1)->pv[0] = MOVE_NONE; - value = -search(pos, ss+1, -beta, -alpha, newDepth, false); + value = -search(pos, ss+1, -beta, -alpha, + std::min(maxNextDepth, newDepth), false); } // Step 18. Undo move @@ -1335,8 +1287,7 @@ namespace { rm.pv.push_back(*m); // We record how often the best move has been changed in each - // iteration. This information is used for time management: when - // the best move changes frequently, we allocate some more time. + // iteration. This information is used for time management and LMR if (moveCount > 1) ++thisThread->bestMoveChanges; } @@ -1363,12 +1314,12 @@ namespace { else { assert(value >= beta); // Fail high - ss->statScore = 0; break; } } } + // If the move is worse than some previously searched move, remember it to update its stats later if (move != bestMove) { if (captureOrPromotion && captureCount < 32) @@ -1395,9 +1346,11 @@ namespace { assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); if (!moveCount) - bestValue = excludedMove ? alpha - : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; + bestValue = excludedMove ? alpha : + ss->inCheck ? mated_in(ss->ply) + : VALUE_DRAW; + // If there is a move which produces search value greater than alpha we update stats of searched moves else if (bestMove) update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, quietsSearched, quietCount, capturesSearched, captureCount, depth); @@ -1419,6 +1372,7 @@ namespace { else if (depth > 3) ss->ttPv = ss->ttPv && (ss+1)->ttPv; + // Write gathered information in transposition table if (!excludedMove && !(rootNode && thisThread->pvIdx)) tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, bestValue >= beta ? BOUND_LOWER : @@ -1433,10 +1387,11 @@ namespace { // qsearch() is the quiescence search function, which is called by the main search // function with zero depth, or recursively with further decreasing depth per call. - template + template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { - constexpr bool PvNode = NT == PV; + static_assert(nodeType != Root); + constexpr bool PvNode = nodeType == PV; assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); @@ -1444,6 +1399,8 @@ namespace { Move pv[MAX_PLY+1]; StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + TTEntry* tte; Key posKey; Move ttMove, move, bestMove; @@ -1460,7 +1417,6 @@ namespace { } Thread* thisThread = pos.this_thread(); - (ss+1)->ply = ss->ply + 1; bestMove = MOVE_NONE; ss->inCheck = pos.checkers(); moveCount = 0; @@ -1512,13 +1468,16 @@ namespace { bestValue = ttValue; } else + // In case of null move search use previous static eval with a different sign + // and addition of two tempos ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) - : -(ss-1)->staticEval + 2 * Tempo; + : -(ss-1)->staticEval; // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { + // Save gathered info in transposition table if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, MOVE_NONE, ss->staticEval); @@ -1529,7 +1488,7 @@ namespace { if (PvNode && bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 145; + futilityBase = bestValue + 155; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1538,7 +1497,7 @@ namespace { // Initialize a MovePicker object for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, - // queen and checking knight promotions, and other checks(only if depth >= DEPTH_QS_CHECKS) + // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, @@ -1555,15 +1514,13 @@ namespace { moveCount++; - // Futility pruning - if ( !ss->inCheck + // Futility pruning and moveCount pruning + if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY && !givesCheck && futilityBase > -VALUE_KNOWN_WIN - && !pos.advanced_pawn_push(move)) + && type_of(move) != PROMOTION) { - assert(type_of(move) != ENPASSANT); // Due to !pos.advanced_pawn_push - // moveCount pruning if (moveCount > 2) continue; @@ -1583,8 +1540,7 @@ namespace { } // Do not search moves with negative SEE values - if ( !ss->inCheck - && !(givesCheck && pos.is_discovery_check_on_king(~pos.side_to_move(), move)) + if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY && !pos.see_ge(move)) continue; @@ -1604,15 +1560,16 @@ namespace { [pos.moved_piece(move)] [to_sq(move)]; + // Continuation history based pruning if ( !captureOrPromotion - && moveCount + && bestValue > VALUE_TB_LOSS_IN_MAX_PLY && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold) continue; // Make and search the move pos.do_move(move, st, givesCheck); - value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); + value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1640,8 +1597,13 @@ namespace { // All legal moves have been searched. A special case: if we're in check // and no legal moves were found, it is checkmate. if (ss->inCheck && bestValue == -VALUE_INFINITE) + { + assert(!MoveList(pos).size()); + return mated_in(ss->ply); // Plies to mate from the root + } + // Save gathered info in transposition table tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, bestValue >= beta ? BOUND_LOWER : PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, @@ -1720,14 +1682,15 @@ namespace { PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); bonus1 = stat_bonus(depth + 1); - bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus - : stat_bonus(depth); // smaller bonus + bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus + : std::min(bonus1, stat_bonus(depth)); // smaller bonus if (!pos.capture_or_promotion(bestMove)) { + // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bonus2, depth); - // Decrease all the non-best quiet moves + // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; @@ -1735,14 +1698,16 @@ namespace { } } else + // Increase stats for the best move in case it was a capture move captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; - // Extra penalty for a quiet early move that was not a TT move or main killer move in previous ply when it gets refuted + // Extra penalty for a quiet early move that was not a TT move or + // main killer move in previous ply when it gets refuted. if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) && !pos.captured_piece()) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); - // Decrease all the non-best capture moves + // Decrease stats for all non-best capture moves for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); @@ -1759,6 +1724,7 @@ namespace { for (int i : {1, 2, 4, 6}) { + // Only update first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; if (is_ok((ss-i)->currentMove)) @@ -1771,6 +1737,7 @@ namespace { void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus, int depth) { + // Update killers if (ss->killers[0] != move) { ss->killers[1] = ss->killers[0]; @@ -1782,15 +1749,18 @@ namespace { thisThread->mainHistory[us][from_to(move)] << bonus; update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); + // Penalty for reversed move in case of moved piece not being a pawn if (type_of(pos.moved_piece(move)) != PAWN) thisThread->mainHistory[us][from_to(reverse_move(move))] << -bonus; + // Update countermove history if (is_ok((ss-1)->currentMove)) { Square prevSq = to_sq((ss-1)->currentMove); thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; } + // Update low ply history if (depth > 11 && ss->ply < MAX_LPH) thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 7); } @@ -1914,10 +1884,6 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { if (elapsed > 1000) // Earlier makes little sense ss << " hashfull " << TT.hashfull(); - if (Eval::nnueHits && Eval::evalHits) { - ss << " nnuehits " << static_cast(Eval::nnueHits * 1000 / Eval::evalHits); - } - ss << " tbhits " << tbHits << " time " << elapsed << " pv"; @@ -1938,6 +1904,8 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { bool RootMove::extract_ponder_from_tt(Position& pos) { StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + bool ttHit; assert(pv.size() == 1); @@ -2005,3 +1973,5 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { m.tbRank = 0; } } + +} // namespace Stockfish diff --git a/engines/stockfish/search.h b/engines/stockfish/search.h index f60da4a..801baac 100644 --- a/engines/stockfish/search.h +++ b/engines/stockfish/search.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,6 +25,8 @@ #include "movepick.h" #include "types.h" +namespace Stockfish { + class Position; namespace Search { @@ -50,6 +52,7 @@ struct Stack { bool inCheck; bool ttPv; bool ttHit; + int doubleExtensions; }; @@ -71,7 +74,6 @@ struct RootMove { Value previousScore = -VALUE_INFINITE; int selDepth = 0; int tbRank = 0; - int bestMoveCount = 0; Value tbScore; std::vector pv; }; @@ -107,4 +109,6 @@ void clear(); } // namespace Search +} // namespace Stockfish + #endif // #ifndef SEARCH_H_INCLUDED diff --git a/engines/stockfish/syzygy/tbprobe.cpp b/engines/stockfish/syzygy/tbprobe.cpp index 4d682f1..96b2970 100644 --- a/engines/stockfish/syzygy/tbprobe.cpp +++ b/engines/stockfish/syzygy/tbprobe.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -50,9 +50,11 @@ #include #endif -using namespace Tablebases; +using namespace Stockfish::Tablebases; -int Tablebases::MaxCardinality; +int Stockfish::Tablebases::MaxCardinality; + +namespace Stockfish { namespace { @@ -103,9 +105,6 @@ template<> inline void swap_endian(uint8_t&) {} template T number(void* addr) { - static const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; - static const bool IsLittleEndian = (Le.c[0] == 4); - T v; if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) @@ -190,7 +189,8 @@ class TBFile : public std::ifstream { std::stringstream ss(Paths); std::string path; - while (std::getline(ss, path, SepChar)) { + while (std::getline(ss, path, SepChar)) + { fname = path + "/" + f; std::ifstream::open(fname); if (is_open()) @@ -565,7 +565,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) { int buf64Size = 64; Sym sym; - while (true) { + while (true) + { int len = 0; // This is the symbol length - d->min_sym_len // Now get the symbol length. For any symbol s64 of length l right-padded @@ -603,8 +604,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // We binary-search for our value recursively expanding into the left and // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 // that will store the value we need. - while (d->symlen[sym]) { - + while (d->symlen[sym]) + { Sym left = d->btree[sym].get(); // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and @@ -709,7 +710,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu leadPawns = b = pos.pieces(color_of(pc), PAWN); do - squares[size++] = pop_lsb(&b) ^ flipSquares; + squares[size++] = pop_lsb(b) ^ flipSquares; while (b); leadPawnsCnt = size; @@ -729,7 +730,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // directly map them to the correct color and square. b = pos.pieces() ^ leadPawns; do { - Square s = pop_lsb(&b); + Square s = pop_lsb(b); squares[size] = s ^ flipSquares; pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); } while (b); @@ -1000,7 +1001,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). // Starting from this we compute a base64[] table indexed by symbol length // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. - // See http://www.eecs.harvard.edu/~michaelm/E210/huffman.pdf + // See https://en.wikipedia.org/wiki/Huffman_coding for (int i = d->base64.size() - 2; i >= 0; --i) { d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) - number(&d->lowestSym[i + 1])) / 2; @@ -1141,7 +1142,7 @@ void* mapped(TBTable& e, const Position& pos) { if (e.ready.load(std::memory_order_acquire)) return e.baseAddress; // Could be nullptr if file does not exist - std::unique_lock lk(mutex); + std::scoped_lock lk(mutex); if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock return e.baseAddress; @@ -1440,7 +1441,7 @@ WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { // If n = 100 immediately after a capture or pawn move, then the position // is also certainly a win, and during the whole phase until the next // capture or pawn move, the inequality to be preserved is -// dtz + 50-movecounter <= 100. +// dtz + 50-move-counter <= 100. // // In short, if a move is available resulting in dtz + 50-move-counter <= 99, // then do not accept moves leading to dtz + 50-move-counter == 100. @@ -1535,6 +1536,14 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { WDLScore wdl = -probe_wdl(pos, &result); dtz = dtz_before_zeroing(wdl); } + else if (pos.is_draw(1)) + { + // In case a root move leads to a draw by repetition or + // 50-move rule, we set dtz to zero. Note: since we are + // only 1 ply from the root, this must be a true 3-fold + // repetition inside the game history. + dtz = 0; + } else { // Otherwise, take dtz for the new position and correct by 1 ply @@ -1585,6 +1594,7 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { ProbeState result; StateInfo st; + WDLScore wdl; bool rule50 = Options["Syzygy50MoveRule"]; @@ -1593,7 +1603,10 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { { pos.do_move(m.pv[0], st); - WDLScore wdl = -probe_wdl(pos, &result); + if (pos.is_draw(1)) + wdl = WDLDraw; + else + wdl = -probe_wdl(pos, &result); pos.undo_move(m.pv[0]); @@ -1610,3 +1623,5 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { return true; } + +} // namespace Stockfish diff --git a/engines/stockfish/syzygy/tbprobe.h b/engines/stockfish/syzygy/tbprobe.h index b998989..56734af 100644 --- a/engines/stockfish/syzygy/tbprobe.h +++ b/engines/stockfish/syzygy/tbprobe.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ #include "../search.h" -namespace Tablebases { +namespace Stockfish::Tablebases { enum WDLScore { WDLLoss = -2, // Loss @@ -73,6 +73,6 @@ inline std::ostream& operator<<(std::ostream& os, const ProbeState v) { return os; } -} +} // namespace Stockfish::Tablebases #endif diff --git a/engines/stockfish/thread.cpp b/engines/stockfish/thread.cpp index b46fce5..da8e1d0 100644 --- a/engines/stockfish/thread.cpp +++ b/engines/stockfish/thread.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,6 +26,8 @@ #include "syzygy/tbprobe.h" #include "tt.h" +namespace Stockfish { + ThreadPool Threads; // Global object @@ -126,14 +128,16 @@ void Thread::idle_loop() { void ThreadPool::set(size_t requested) { - if (size() > 0) { // destroy any existing thread(s) + if (size() > 0) // destroy any existing thread(s) + { main()->wait_for_search_finished(); while (size() > 0) delete back(), pop_back(); } - if (requested > 0) { // create new thread(s) + if (requested > 0) // create new thread(s) + { push_back(new MainThread(0)); while (size() < requested) @@ -224,16 +228,16 @@ Thread* ThreadPool::get_best_thread() const { votes[th->rootMoves[0].pv[0]] += (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); - if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) - { - // Make sure we pick the shortest mate / TB conversion or stave off mate the longest - if (th->rootMoves[0].score > bestThread->rootMoves[0].score) - bestThread = th; - } - else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY - || ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY - && votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]])) - bestThread = th; + if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) + { + // Make sure we pick the shortest mate / TB conversion or stave off mate the longest + if (th->rootMoves[0].score > bestThread->rootMoves[0].score) + bestThread = th; + } + else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY + || ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY + && votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]])) + bestThread = th; } return bestThread; @@ -258,3 +262,5 @@ void ThreadPool::wait_for_search_finished() const { if (th != front()) th->wait_for_search_finished(); } + +} // namespace Stockfish diff --git a/engines/stockfish/thread.h b/engines/stockfish/thread.h index 34b9901..5bfa235 100644 --- a/engines/stockfish/thread.h +++ b/engines/stockfish/thread.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,6 +32,7 @@ #include "search.h" #include "thread_win32_osx.h" +namespace Stockfish { /// Thread class keeps together all the thread-related stuff. We use /// per-thread pawn and material hash tables so that once we get a @@ -54,6 +55,7 @@ class Thread { void idle_loop(); void start_searching(); void wait_for_search_finished(); + size_t id() const { return idx; } Pawns::Table pawnsTable; Material::Table materialTable; @@ -72,7 +74,7 @@ class Thread { LowPlyHistory lowPlyHistory; CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; - Score contempt; + Score trend; }; @@ -127,4 +129,6 @@ struct ThreadPool : public std::vector { extern ThreadPool Threads; +} // namespace Stockfish + #endif // #ifndef THREAD_H_INCLUDED diff --git a/engines/stockfish/thread_win32_osx.h b/engines/stockfish/thread_win32_osx.h index 75ef5d9..a21674c 100644 --- a/engines/stockfish/thread_win32_osx.h +++ b/engines/stockfish/thread_win32_osx.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,6 +31,8 @@ #include +namespace Stockfish { + static const size_t TH_STACK_SIZE = 8 * 1024 * 1024; template > @@ -57,10 +59,16 @@ class NativeThread { void join() { pthread_join(thread, NULL); } }; +} // namespace Stockfish + #else // Default case: use STL classes +namespace Stockfish { + typedef std::thread NativeThread; +} // namespace Stockfish + #endif #endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED diff --git a/engines/stockfish/timeman.cpp b/engines/stockfish/timeman.cpp index 6d9c95e..f742d1e 100644 --- a/engines/stockfish/timeman.cpp +++ b/engines/stockfish/timeman.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ #include "timeman.h" #include "uci.h" +namespace Stockfish { + TimeManagement Time; // Our global time management object @@ -75,7 +77,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { // game time for the current move, so also cap to 20% of available game time. if (limits.movestogo == 0) { - optScale = std::min(0.008 + std::pow(ply + 3.0, 0.5) / 250.0, + optScale = std::min(0.0084 + std::pow(ply + 3.0, 0.5) * 0.0042, 0.2 * limits.time[us] / double(timeLeft)); maxScale = std::min(7.0, 4.0 + ply / 12.0); } @@ -95,3 +97,5 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { if (Options["Ponder"]) optimumTime += optimumTime / 4; } + +} // namespace Stockfish diff --git a/engines/stockfish/timeman.h b/engines/stockfish/timeman.h index 5ad72b3..b1878d6 100644 --- a/engines/stockfish/timeman.h +++ b/engines/stockfish/timeman.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ #include "search.h" #include "thread.h" +namespace Stockfish { + /// The TimeManagement class computes the optimal time to think depending on /// the maximum available time, the game move number and other parameters. @@ -44,4 +46,6 @@ class TimeManagement { extern TimeManagement Time; +} // namespace Stockfish + #endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/engines/stockfish/tt.cpp b/engines/stockfish/tt.cpp index dea7c71..1f495ca 100644 --- a/engines/stockfish/tt.cpp +++ b/engines/stockfish/tt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,6 +26,8 @@ #include "tt.h" #include "uci.h" +namespace Stockfish { + TranspositionTable TT; // Our global transposition table /// TTEntry::save() populates the TTEntry with a new node's data, possibly @@ -123,7 +125,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { for (int i = 0; i < ClusterSize; ++i) if (tte[i].key16 == key16 || !tte[i].depth8) { - tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & 0x7)); // Refresh + tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh return found = (bool)tte[i].depth8, &tte[i]; } @@ -132,11 +134,12 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { TTEntry* replace = tte; for (int i = 1; i < ClusterSize; ++i) // Due to our packed storage format for generation and its cyclic - // nature we add 263 (256 is the modulus plus 7 to keep the unrelated - // lowest three bits from affecting the result) to calculate the entry - // age correctly even after generation8 overflows into the next cycle. - if ( replace->depth8 - ((263 + generation8 - replace->genBound8) & 0xF8) - > tte[i].depth8 - ((263 + generation8 - tte[i].genBound8) & 0xF8)) + // nature we add GENERATION_CYCLE (256 is the modulus, plus what + // is needed to keep the unrelated lowest n bits from affecting + // the result) to calculate the entry age correctly even after + // generation8 overflows into the next cycle. + if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) + > tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) replace = &tte[i]; return found = false, replace; @@ -151,7 +154,9 @@ int TranspositionTable::hashfull() const { int cnt = 0; for (int i = 0; i < 1000; ++i) for (int j = 0; j < ClusterSize; ++j) - cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & 0xF8) == generation8; + cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; return cnt / ClusterSize; } + +} // namespace Stockfish diff --git a/engines/stockfish/tt.h b/engines/stockfish/tt.h index 6aa066c..d915d92 100644 --- a/engines/stockfish/tt.h +++ b/engines/stockfish/tt.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,6 +22,8 @@ #include "misc.h" #include "types.h" +namespace Stockfish { + /// TTEntry struct is the 10 bytes transposition table entry, defined as below: /// /// key 16 bit @@ -72,9 +74,15 @@ class TranspositionTable { static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); + // Constants used to refresh the hash table periodically + static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things + static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); // increment for generation field + static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length + static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number + public: ~TranspositionTable() { aligned_large_pages_free(table); } - void new_search() { generation8 += 8; } // Lower 3 bits are used by PV flag and Bound + void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things TTEntry* probe(const Key key, bool& found) const; int hashfull() const; void resize(size_t mbSize); @@ -94,4 +102,6 @@ class TranspositionTable { extern TranspositionTable TT; +} // namespace Stockfish + #endif // #ifndef TT_H_INCLUDED diff --git a/engines/stockfish/tune.cpp b/engines/stockfish/tune.cpp index e94f67f..ac91b60 100644 --- a/engines/stockfish/tune.cpp +++ b/engines/stockfish/tune.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,9 +26,10 @@ using std::string; +namespace Stockfish { + bool Tune::update_on_last; const UCI::Option* LastOption = nullptr; -BoolConditions Conditions; static std::map TuneResults; string Tune::next(string& names, bool pop) { @@ -108,23 +109,7 @@ template<> void Tune::Entry::read_option() { template<> void Tune::Entry::init_option() {} template<> void Tune::Entry::read_option() { value(); } - -// Set binary conditions according to a probability that depends -// on the corresponding parameter value. - -void BoolConditions::set() { - - static PRNG rng(now()); - static bool startup = true; // To workaround fishtest bench - - for (size_t i = 0; i < binary.size(); i++) - binary[i] = !startup && (values[i] + int(rng.rand() % variance) > threshold); - - startup = false; - - for (size_t i = 0; i < binary.size(); i++) - sync_cout << binary[i] << sync_endl; -} +} // namespace Stockfish // Init options with tuning session results instead of default values. Useful to @@ -138,7 +123,11 @@ void BoolConditions::set() { #include +namespace Stockfish { + void Tune::read_results() { /* ...insert your values here... */ } + +} // namespace Stockfish diff --git a/engines/stockfish/tune.h b/engines/stockfish/tune.h index 1489fa3..b5c715b 100644 --- a/engines/stockfish/tune.h +++ b/engines/stockfish/tune.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ #include #include +namespace Stockfish { + typedef std::pair Range; // Option's min-max values typedef Range (RangeFun) (int); @@ -44,27 +46,6 @@ struct SetRange { #define SetDefaultRange SetRange(default_range) -/// BoolConditions struct is used to tune boolean conditions in the -/// code by toggling them on/off according to a probability that -/// depends on the value of a tuned integer parameter: for high -/// values of the parameter condition is always disabled, for low -/// values is always enabled, otherwise it is enabled with a given -/// probability that depnends on the parameter under tuning. - -struct BoolConditions { - void init(size_t size) { values.resize(size, defaultValue), binary.resize(size, 0); } - void set(); - - std::vector binary, values; - int defaultValue = 465, variance = 40, threshold = 500; - SetRange range = SetRange(0, 1000); -}; - -extern BoolConditions Conditions; - -inline void set_conditions() { Conditions.set(); } - - /// Tune class implements the 'magic' code that makes the setup of a fishtest /// tuning session as easy as it can be. Mainly you have just to remove const /// qualifiers from the variables you want to tune and flag them for tuning, so @@ -130,9 +111,9 @@ class Tune { SetRange range; }; - // Our facilty to fill the container, each Entry corresponds to a parameter to tune. - // We use variadic templates to deal with an unspecified number of entries, each one - // of a possible different type. + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. static std::string next(std::string& names, bool pop = true); int add(const SetRange&, std::string&&) { return 0; } @@ -157,14 +138,6 @@ class Tune { return add(value, (next(names), std::move(names)), args...); } - // Template specialization for BoolConditions - template - int add(const SetRange& range, std::string&& names, BoolConditions& cond, Args&&... args) { - for (size_t size = cond.values.size(), i = 0; i < size; i++) - add(cond.range, next(names, i == size - 1) + "_" + std::to_string(i), cond.values[i]); - return add(range, std::move(names), args...); - } - std::vector> list; public: @@ -185,9 +158,6 @@ class Tune { #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true -// Some macro to tune toggling of boolean conditions -#define CONDITION(x) (Conditions.binary[__COUNTER__] || (x)) -#define TUNE_CONDITIONS() int UNIQUE(c, __LINE__) = (Conditions.init(__COUNTER__), 0); \ - TUNE(Conditions, set_conditions) +} // namespace Stockfish #endif // #ifndef TUNE_H_INCLUDED diff --git a/engines/stockfish/types.h b/engines/stockfish/types.h index 5873c69..0bd4a1c 100644 --- a/engines/stockfish/types.h +++ b/engines/stockfish/types.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -57,6 +57,12 @@ /// _WIN32 Building on Windows (any) /// _WIN64 Building on Windows 64 bit +#if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__) +#define ALIGNAS_ON_STACK_VARIABLES_BROKEN +#endif + +#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) + #if defined(_WIN64) && defined(_MSC_VER) // No Makefile used # include // Microsoft header for _BitScanForward64() # define IS_64BIT @@ -77,6 +83,8 @@ # define pext(b, m) 0 #endif +namespace Stockfish { + #ifdef USE_POPCNT constexpr bool HasPopCnt = true; #else @@ -107,7 +115,7 @@ constexpr int MAX_PLY = 246; /// bit 6-11: origin square (from 0 to 63) /// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) /// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) -/// NOTE: EN-PASSANT bit is set only when a pawn can be captured +/// NOTE: en passant bit is set only when a pawn can be captured /// /// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in /// any normal move destination square is always different from origin square @@ -121,7 +129,7 @@ enum Move : int { enum MoveType { NORMAL, PROMOTION = 1 << 14, - ENPASSANT = 2 << 14, + EN_PASSANT = 2 << 14, CASTLING = 3 << 14 }; @@ -183,7 +191,6 @@ enum Value : int { BishopValueMg = 825, BishopValueEg = 915, RookValueMg = 1276, RookValueEg = 1380, QueenValueMg = 2538, QueenValueEg = 2682, - Tempo = 28, MidgameLimit = 15258, EndgameLimit = 3915 }; @@ -196,8 +203,8 @@ enum PieceType { enum Piece { NO_PIECE, - W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, + W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, PIECE_NB = 16 }; @@ -476,6 +483,8 @@ constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; } +} // namespace Stockfish + #endif // #ifndef TYPES_H_INCLUDED #include "tune.h" // Global visibility to tuning setup diff --git a/engines/stockfish/uci.cpp b/engines/stockfish/uci.cpp index e2d0c4d..1038ab9 100644 --- a/engines/stockfish/uci.cpp +++ b/engines/stockfish/uci.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,21 +32,25 @@ #include "uci.h" #include "syzygy/tbprobe.h" + +// Added for BanksiaGUI #include "engineids.h" void engine_message(int eid, const std::string& s); using namespace std; +namespace Stockfish { + extern vector setup_bench(const Position&, istream&); -//void search_message(const std::string& str); //namespace { // FEN string of the initial position, normal chess const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - bool benchworking = false; + // Added by BanksiaGUI + bool benchworking = false; // position() is called when engine receives the "position" UCI command. // The function sets up the position described in the given FEN string ("fen") @@ -91,7 +95,7 @@ extern vector setup_bench(const Position&, istream&); Position p; p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); - Eval::verify_NNUE(); + Eval::NNUE::verify(); sync_cout << "\n" << Eval::trace(p) << sync_endl; } @@ -169,12 +173,8 @@ extern vector setup_bench(const Position&, istream&); TimePoint elapsed = now(); - benchworking = true; for (const auto& cmd : list) { - if (!benchworking) { - break; - } istringstream is(cmd); is >> skipws >> token; @@ -194,21 +194,27 @@ extern vector setup_bench(const Position&, istream&); else if (token == "position") position(pos, is, states); else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take some while } - benchworking = false; + elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' dbg_print(); // Just before exiting - auto s = "Total time (ms) : " + std::to_string(elapsed) - + "\nNodes searched : " + std::to_string(nodes) - + "\nNodes/second : " + std::to_string(1000 * nodes / elapsed); - engine_message(stockfish, s); - - cerr << "\n===========================\n" << s << std::endl; - - s = "bench END"; - std::cout << s << std::endl; - engine_message(stockfish, s); +// cerr << "\n===========================" +// << "\nTotal time (ms) : " << elapsed +// << "\nNodes searched : " << nodes +// << "\nNodes/second : " << 1000 * nodes / elapsed << endl; + + // Added for BanksiaGUI + auto s = "Total time (ms) : " + std::to_string(elapsed) + + "\nNodes searched : " + std::to_string(nodes) + + "\nNodes/second : " + std::to_string(1000 * nodes / elapsed); + engine_message(stockfish, s); + + cerr << "\n===========================\n" << s << std::endl; + + s = "bench END"; + std::cout << s << std::endl; + engine_message(stockfish, s); } // The win rate model returns the probability (per mille) of winning given an eval @@ -221,13 +227,13 @@ extern vector setup_bench(const Position&, istream&); // Coefficients of a 3rd order polynomial fit based on fishtest data // for two parameters needed to transform eval to the argument of a // logistic function. - double as[] = {-8.24404295, 64.23892342, -95.73056462, 153.86478679}; - double bs[] = {-3.37154371, 28.44489198, -56.67657741, 72.05858751}; + double as[] = {-3.68389304, 30.07065921, -60.52878723, 149.53378557}; + double bs[] = {-2.0181857, 15.85685038, -29.83452023, 47.59078827}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; // Transform eval to centipawns with limited range - double x = std::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0); + double x = std::clamp(double(100 * v) / PawnValueEg, -2000.0, 2000.0); // Return win rate in per mille (rounded to nearest) return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); @@ -263,9 +269,8 @@ void UCI::loop(int argc, char* argv[]) { is >> skipws >> token; if ( token == "quit" - || token == "stop") { + || token == "stop") Threads.stop = true; - } // The GUI sends 'ponderhit' to tell us the user has played the expected move. // So 'ponderhit' will be sent if we were told to ponder on the same move the @@ -292,7 +297,15 @@ void UCI::loop(int argc, char* argv[]) { else if (token == "d") sync_cout << pos << sync_endl; else if (token == "eval") trace_eval(pos); else if (token == "compiler") sync_cout << compiler_info() << sync_endl; - else + else if (token == "export_net") + { + std::optional filename; + std::string f; + if (is >> skipws >> f) + filename = f; + Eval::NNUE::save_eval(filename); + } + else if (!token.empty() && token[0] != '#') sync_cout << "Unknown command: " << cmd << sync_endl; } while (token != "quit" && argc == 1); // Command line args are one-shot @@ -386,3 +399,5 @@ Move UCI::to_move(const Position& pos, string& str) { return MOVE_NONE; } + +} // namespace Stockfish diff --git a/engines/stockfish/uci.h b/engines/stockfish/uci.h index 3d05193..3eb57f3 100644 --- a/engines/stockfish/uci.h +++ b/engines/stockfish/uci.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ #include "types.h" +namespace Stockfish { + class Position; namespace UCI { @@ -73,10 +75,14 @@ std::string move(Move m, bool chess960); std::string pv(const Position& pos, Depth depth, Value alpha, Value beta); std::string wdl(Value v, int ply); Move to_move(const Position& pos, std::string& str); + +// Added by BanksiaGUI int cmd(const std::string& cmd); } // namespace UCI extern UCI::OptionsMap Options; +} // namespace Stockfish + #endif // #ifndef UCI_H_INCLUDED diff --git a/engines/stockfish/ucioption.cpp b/engines/stockfish/ucioption.cpp index f059209..07b3027 100644 --- a/engines/stockfish/ucioption.cpp +++ b/engines/stockfish/ucioption.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,6 +31,8 @@ using std::string; +namespace Stockfish { + UCI::OptionsMap Options; // Global object namespace UCI { @@ -41,8 +43,8 @@ void on_hash_size(const Option& o) { TT.resize(size_t(o)); } void on_logger(const Option& o) { start_logger(o); } void on_threads(const Option& o) { Threads.set(size_t(o)); } void on_tb_path(const Option& o) { Tablebases::init(o); } -void on_use_NNUE(const Option& ) { Eval::init_NNUE(); } -void on_eval_file(const Option& ) { Eval::init_NNUE(); } +void on_use_NNUE(const Option& ) { Eval::NNUE::init(); } +void on_eval_file(const Option& ) { Eval::NNUE::init(); } /// Our case insensitive less() function as required by UCI protocol bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { @@ -59,8 +61,6 @@ void init(OptionsMap& o) { constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; o["Debug Log File"] << Option("", on_logger); - o["Contempt"] << Option(24, -100, 100); - o["Analysis Contempt"] << Option("Both var Off var White var Black var Both", "Both"); o["Threads"] << Option(1, 1, 512, on_threads); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Clear Hash"] << Option(on_clear_hash); @@ -79,7 +79,7 @@ void init(OptionsMap& o) { o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(7, 0, 7); - o["Use NNUE"] << Option(0, 0, 2, on_use_NNUE); + o["Use NNUE"] << Option(true, on_use_NNUE); o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); } @@ -190,3 +190,5 @@ Option& Option::operator=(const string& v) { } } // namespace UCI + +} // namespace Stockfish diff --git a/engines/stockfish13/Makefile b/engines/stockfish13/Makefile new file mode 100644 index 0000000..54868b3 --- /dev/null +++ b/engines/stockfish13/Makefile @@ -0,0 +1,884 @@ +# Stockfish, a UCI chess playing engine derived from Glaurung 2.1 +# Copyright (C) 2004-2008 Tord Romstad (Glaurung author) +# Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad +# Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad +# +# Stockfish is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Stockfish is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +### ========================================================================== +### Section 1. General Configuration +### ========================================================================== + +### Executable name +ifeq ($(COMP),mingw) +EXE = stockfish.exe +else +EXE = stockfish +endif + +### Installation dir definitions +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin + +### Built-in benchmark for pgo-builds +PGOBENCH = ./$(EXE) bench + +### Source and object files +SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \ + material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ + search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ + nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp + +OBJS = $(notdir $(SRCS:.cpp=.o)) + +VPATH = syzygy:nnue:nnue/features + +### Establish the operating system name +KERNEL = $(shell uname -s) +ifeq ($(KERNEL),Linux) + OS = $(shell uname -o) +endif + +### ========================================================================== +### Section 2. High-level Configuration +### ========================================================================== +# +# flag --- Comp switch --- Description +# ---------------------------------------------------------------------------- +# +# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode +# sanitize = undefined/thread/no (-fsanitize ) +# --- ( undefined ) --- enable undefined behavior checks +# --- ( thread ) --- enable threading error checks +# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations +# arch = (name) --- (-arch) --- Target architecture +# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system +# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction +# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction +# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction +# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions +# mmx = yes/no --- -mmmx --- Use Intel MMX instructions +# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2 +# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3 +# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1 +# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 +# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 +# vnni256 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256 +# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 +# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture +# +# Note that Makefile is space sensitive, so when adding new architectures +# or modifying existing flags, you have to make sure there are no extra spaces +# at the end of the line for flag values. + +### 2.1. General and architecture defaults + +ifeq ($(ARCH),) + ARCH = x86-64-modern + help_skip_sanity = yes +endif +# explicitly check for the list of supported architectures (as listed with make help), +# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true` +ifeq ($(ARCH), $(filter $(ARCH), \ + x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-bmi2 x86-64-avx2 \ + x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ + x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 \ + armv7 armv7-neon armv8 apple-silicon general-64 general-32)) + SUPPORTED_ARCH=true +else + SUPPORTED_ARCH=false +endif + +optimize = yes +debug = no +sanitize = no +bits = 64 +prefetch = no +popcnt = no +pext = no +sse = no +mmx = no +sse2 = no +ssse3 = no +sse41 = no +avx2 = no +avx512 = no +vnni256 = no +vnni512 = no +neon = no +STRIP = strip + +### 2.2 Architecture specific + +ifeq ($(findstring x86,$(ARCH)),x86) + +# x86-32/64 + +ifeq ($(findstring x86-32,$(ARCH)),x86-32) + arch = i386 + bits = 32 + sse = yes + mmx = yes +else + arch = x86_64 + sse = yes + sse2 = yes +endif + +ifeq ($(findstring -sse,$(ARCH)),-sse) + sse = yes +endif + +ifeq ($(findstring -popcnt,$(ARCH)),-popcnt) + popcnt = yes +endif + +ifeq ($(findstring -mmx,$(ARCH)),-mmx) + mmx = yes +endif + +ifeq ($(findstring -sse2,$(ARCH)),-sse2) + sse = yes + sse2 = yes +endif + +ifeq ($(findstring -ssse3,$(ARCH)),-ssse3) + sse = yes + sse2 = yes + ssse3 = yes +endif + +ifeq ($(findstring -sse41,$(ARCH)),-sse41) + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes +endif + +ifeq ($(findstring -modern,$(ARCH)),-modern) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes +endif + +ifeq ($(findstring -avx2,$(ARCH)),-avx2) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes +endif + +ifeq ($(findstring -bmi2,$(ARCH)),-bmi2) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes +endif + +ifeq ($(findstring -avx512,$(ARCH)),-avx512) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + avx512 = yes +endif + +ifeq ($(findstring -vnni256,$(ARCH)),-vnni256) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + vnni256 = yes +endif + +ifeq ($(findstring -vnni512,$(ARCH)),-vnni512) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + avx512 = yes + vnni512 = yes +endif + +ifeq ($(sse),yes) + prefetch = yes +endif + +# 64-bit pext is not available on x86-32 +ifeq ($(bits),32) + pext = no +endif + +else + +# all other architectures + +ifeq ($(ARCH),general-32) + arch = any + bits = 32 +endif + +ifeq ($(ARCH),general-64) + arch = any +endif + +ifeq ($(ARCH),armv7) + arch = armv7 + prefetch = yes + bits = 32 +endif + +ifeq ($(ARCH),armv7-neon) + arch = armv7 + prefetch = yes + popcnt = yes + neon = yes + bits = 32 +endif + +ifeq ($(ARCH),armv8) + arch = armv8 + prefetch = yes + popcnt = yes + neon = yes +endif + +ifeq ($(ARCH),apple-silicon) + arch = arm64 + prefetch = yes + popcnt = yes + neon = yes +endif + +ifeq ($(ARCH),ppc-32) + arch = ppc + bits = 32 +endif + +ifeq ($(ARCH),ppc-64) + arch = ppc64 + popcnt = yes + prefetch = yes +endif + +endif + +### ========================================================================== +### Section 3. Low-level Configuration +### ========================================================================== + +### 3.1 Selecting compiler (default = gcc) +CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS) +DEPENDFLAGS += -std=c++17 +LDFLAGS += $(EXTRALDFLAGS) + +ifeq ($(COMP),) + COMP=gcc +endif + +ifeq ($(COMP),gcc) + comp=gcc + CXX=g++ + CXXFLAGS += -pedantic -Wextra -Wshadow + + ifeq ($(arch),$(filter $(arch),armv7 armv8)) + ifeq ($(OS),Android) + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + else + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + + ifeq ($(arch),$(filter $(arch),armv7)) + LDFLAGS += -latomic + endif + + ifneq ($(KERNEL),Darwin) + LDFLAGS += -Wl,--no-as-needed + endif +endif + +ifeq ($(COMP),mingw) + comp=mingw + + ifeq ($(KERNEL),Linux) + ifeq ($(bits),64) + ifeq ($(shell which x86_64-w64-mingw32-c++-posix),) + CXX=x86_64-w64-mingw32-c++ + else + CXX=x86_64-w64-mingw32-c++-posix + endif + else + ifeq ($(shell which i686-w64-mingw32-c++-posix),) + CXX=i686-w64-mingw32-c++ + else + CXX=i686-w64-mingw32-c++-posix + endif + endif + else + CXX=g++ + endif + + CXXFLAGS += -Wextra -Wshadow + LDFLAGS += -static +endif + +ifeq ($(COMP),icc) + comp=icc + CXX=icpc + CXXFLAGS += -diag-disable 1476,10120 -Wcheck -Wabi -Wdeprecated -strict-ansi +endif + +ifeq ($(COMP),clang) + comp=clang + CXX=clang++ + CXXFLAGS += -pedantic -Wextra -Wshadow + + ifneq ($(KERNEL),Darwin) + ifneq ($(KERNEL),OpenBSD) + LDFLAGS += -latomic + endif + endif + + ifeq ($(arch),$(filter $(arch),armv7 armv8)) + ifeq ($(OS),Android) + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + else + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif +endif + +ifeq ($(KERNEL),Darwin) + CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.14 + LDFLAGS += -arch $(arch) -mmacosx-version-min=10.14 + XCRUN = xcrun +endif + +# To cross-compile for Android, NDK version r21 or later is recommended. +# In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils. +# Currently we don't know how to make PGO builds with the NDK yet. +ifeq ($(COMP),ndk) + CXXFLAGS += -stdlib=libc++ -fPIE + comp=clang + ifeq ($(arch),armv7) + CXX=armv7a-linux-androideabi16-clang++ + CXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon + STRIP=arm-linux-androideabi-strip + endif + ifeq ($(arch),armv8) + CXX=aarch64-linux-android21-clang++ + STRIP=aarch64-linux-android-strip + endif + LDFLAGS += -static-libstdc++ -pie -lm -latomic +endif + +ifeq ($(comp),icc) + profile_make = icc-profile-make + profile_use = icc-profile-use +else ifeq ($(comp),clang) + profile_make = clang-profile-make + profile_use = clang-profile-use +else + profile_make = gcc-profile-make + profile_use = gcc-profile-use +endif + +### Travis CI script uses COMPILER to overwrite CXX +ifdef COMPILER + COMPCXX=$(COMPILER) +endif + +### Allow overwriting CXX from command line +ifdef COMPCXX + CXX=$(COMPCXX) +endif + +### Sometimes gcc is really clang +ifeq ($(COMP),gcc) + gccversion = $(shell $(CXX) --version) + gccisclang = $(findstring clang,$(gccversion)) + ifneq ($(gccisclang),) + profile_make = clang-profile-make + profile_use = clang-profile-use + endif +endif + +### On mingw use Windows threads, otherwise POSIX +ifneq ($(comp),mingw) + CXXFLAGS += -DUSE_PTHREADS + # On Android Bionic's C library comes with its own pthread implementation bundled in + ifneq ($(OS),Android) + # Haiku has pthreads in its libroot, so only link it in on other platforms + ifneq ($(KERNEL),Haiku) + ifneq ($(COMP),ndk) + LDFLAGS += -lpthread + endif + endif + endif +endif + +### 3.2.1 Debugging +ifeq ($(debug),no) + CXXFLAGS += -DNDEBUG +else + CXXFLAGS += -g +endif + +### 3.2.2 Debugging with undefined behavior sanitizers +ifneq ($(sanitize),no) + CXXFLAGS += -g3 -fsanitize=$(sanitize) + LDFLAGS += -fsanitize=$(sanitize) +endif + +### 3.3 Optimization +ifeq ($(optimize),yes) + + CXXFLAGS += -O3 + + ifeq ($(comp),gcc) + ifeq ($(OS), Android) + CXXFLAGS += -fno-gcse -mthumb -march=armv7-a -mfloat-abi=softfp + endif + endif + + ifeq ($(comp),$(filter $(comp),gcc clang icc)) + ifeq ($(KERNEL),Darwin) + CXXFLAGS += -mdynamic-no-pic + endif + endif +endif + +### 3.4 Bits +ifeq ($(bits),64) + CXXFLAGS += -DIS_64BIT +endif + +### 3.5 prefetch +ifeq ($(prefetch),yes) + ifeq ($(sse),yes) + CXXFLAGS += -msse + endif +else + CXXFLAGS += -DNO_PREFETCH +endif + +### 3.6 popcnt +ifeq ($(popcnt),yes) + ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64)) + CXXFLAGS += -DUSE_POPCNT + else ifeq ($(comp),icc) + CXXFLAGS += -msse3 -DUSE_POPCNT + else + CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT + endif +endif + + +ifeq ($(avx2),yes) + CXXFLAGS += -DUSE_AVX2 + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mavx2 + endif +endif + +ifeq ($(avx512),yes) + CXXFLAGS += -DUSE_AVX512 + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mavx512f -mavx512bw + endif +endif + +ifeq ($(vnni256),yes) + CXXFLAGS += -DUSE_VNNI + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256 + endif +endif + +ifeq ($(vnni512),yes) + CXXFLAGS += -DUSE_VNNI + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mavx512vnni -mavx512dq -mavx512vl + endif +endif + +ifeq ($(sse41),yes) + CXXFLAGS += -DUSE_SSE41 + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -msse4.1 + endif +endif + +ifeq ($(ssse3),yes) + CXXFLAGS += -DUSE_SSSE3 + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mssse3 + endif +endif + +ifeq ($(sse2),yes) + CXXFLAGS += -DUSE_SSE2 + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -msse2 + endif +endif + +ifeq ($(mmx),yes) + CXXFLAGS += -DUSE_MMX + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mmmx + endif +endif + +ifeq ($(neon),yes) + CXXFLAGS += -DUSE_NEON + ifeq ($(KERNEL),Linux) + ifneq ($(COMP),ndk) + ifneq ($(arch),armv8) + CXXFLAGS += -mfpu=neon + endif + endif + endif +endif + +### 3.7 pext +ifeq ($(pext),yes) + CXXFLAGS += -DUSE_PEXT + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mbmi2 + endif +endif + +### 3.8 Link Time Optimization +### This is a mix of compile and link time options because the lto link phase +### needs access to the optimization flags. +ifeq ($(optimize),yes) +ifeq ($(debug), no) + ifeq ($(comp),clang) + CXXFLAGS += -flto=thin + ifneq ($(findstring MINGW,$(KERNEL)),) + CXXFLAGS += -fuse-ld=lld + else ifneq ($(findstring MSYS,$(KERNEL)),) + CXXFLAGS += -fuse-ld=lld + endif + LDFLAGS += $(CXXFLAGS) + +# GCC and CLANG use different methods for parallelizing LTO and CLANG pretends to be +# GCC on some systems. + else ifeq ($(comp),gcc) + ifeq ($(gccisclang),) + CXXFLAGS += -flto + LDFLAGS += $(CXXFLAGS) -flto=jobserver + ifneq ($(findstring MINGW,$(KERNEL)),) + LDFLAGS += -save-temps + else ifneq ($(findstring MSYS,$(KERNEL)),) + LDFLAGS += -save-temps + endif + else + CXXFLAGS += -flto=thin + LDFLAGS += $(CXXFLAGS) + endif + +# To use LTO and static linking on windows, the tool chain requires a recent gcc: +# gcc version 10.1 in msys2 or TDM-GCC version 9.2 are known to work, older might not. +# So, only enable it for a cross from Linux by default. + else ifeq ($(comp),mingw) + ifeq ($(KERNEL),Linux) + ifneq ($(arch),i386) + CXXFLAGS += -flto + LDFLAGS += $(CXXFLAGS) -flto=jobserver + endif + endif + endif +endif +endif + +### 3.9 Android 5 can only run position independent executables. Note that this +### breaks Android 4.0 and earlier. +ifeq ($(OS), Android) + CXXFLAGS += -fPIE + LDFLAGS += -fPIE -pie +endif + +### ========================================================================== +### Section 4. Public Targets +### ========================================================================== + + +help: + @echo "" + @echo "To compile stockfish, type: " + @echo "" + @echo "make target ARCH=arch [COMP=compiler] [COMPCXX=cxx]" + @echo "" + @echo "Supported targets:" + @echo "" + @echo "help > Display architecture details" + @echo "build > Standard build" + @echo "net > Download the default nnue net" + @echo "profile-build > Faster build (with profile-guided optimization)" + @echo "strip > Strip executable" + @echo "install > Install executable" + @echo "clean > Clean up" + @echo "" + @echo "Supported archs:" + @echo "" + @echo "x86-64-vnni512 > x86 64-bit with vnni support 512bit wide" + @echo "x86-64-vnni256 > x86 64-bit with vnni support 256bit wide" + @echo "x86-64-avx512 > x86 64-bit with avx512 support" + @echo "x86-64-bmi2 > x86 64-bit with bmi2 support" + @echo "x86-64-avx2 > x86 64-bit with avx2 support" + @echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" + @echo "x86-64-modern > common modern CPU, currently x86-64-sse41-popcnt" + @echo "x86-64-ssse3 > x86 64-bit with ssse3 support" + @echo "x86-64-sse3-popcnt > x86 64-bit with sse3 and popcnt support" + @echo "x86-64 > x86 64-bit generic (with sse2 support)" + @echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support" + @echo "x86-32-sse2 > x86 32-bit with sse2 support" + @echo "x86-32 > x86 32-bit generic (with mmx and sse support)" + @echo "ppc-64 > PPC 64-bit" + @echo "ppc-32 > PPC 32-bit" + @echo "armv7 > ARMv7 32-bit" + @echo "armv7-neon > ARMv7 32-bit with popcnt and neon" + @echo "armv8 > ARMv8 64-bit with popcnt and neon" + @echo "apple-silicon > Apple silicon ARM64" + @echo "general-64 > unspecified 64-bit" + @echo "general-32 > unspecified 32-bit" + @echo "" + @echo "Supported compilers:" + @echo "" + @echo "gcc > Gnu compiler (default)" + @echo "mingw > Gnu compiler with MinGW under Windows" + @echo "clang > LLVM Clang compiler" + @echo "icc > Intel compiler" + @echo "ndk > Google NDK to cross-compile for Android" + @echo "" + @echo "Simple examples. If you don't know what to do, you likely want to run: " + @echo "" + @echo "make -j build ARCH=x86-64 (A portable, slow compile for 64-bit systems)" + @echo "make -j build ARCH=x86-32 (A portable, slow compile for 32-bit systems)" + @echo "" + @echo "Advanced examples, for experienced users looking for performance: " + @echo "" + @echo "make help ARCH=x86-64-bmi2" + @echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0" + @echo "make -j build ARCH=x86-64-ssse3 COMP=clang" + @echo "" + @echo "-------------------------------" +ifeq ($(SUPPORTED_ARCH)$(help_skip_sanity), true) + @echo "The selected architecture $(ARCH) will enable the following configuration: " + @$(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity +else + @echo "Specify a supported architecture with the ARCH option for more details" + @echo "" +endif + + +.PHONY: help build profile-build strip install clean net objclean profileclean \ + config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \ + clang-profile-use clang-profile-make + +build: config-sanity net + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all + +profile-build: net config-sanity objclean profileclean + @echo "" + @echo "Step 1/4. Building instrumented executable ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) + @echo "" + @echo "Step 2/4. Running benchmark for pgo-build ..." + $(PGOBENCH) > /dev/null + @echo "" + @echo "Step 3/4. Building optimized executable ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use) + @echo "" + @echo "Step 4/4. Deleting profile data ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean + +strip: + $(STRIP) $(EXE) + +install: + -mkdir -p -m 755 $(BINDIR) + -cp $(EXE) $(BINDIR) + -strip $(BINDIR)/$(EXE) + +# clean all +clean: objclean profileclean + @rm -f .depend *~ core + +# evaluation network (nnue) +net: + $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) + @echo "Default net: $(nnuenet)" + $(eval nnuedownloadurl := https://tests.stockfishchess.org/api/nn/$(nnuenet)) + $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) + @if test -f "$(nnuenet)"; then \ + echo "Already available."; \ + else \ + if [ "x$(curl_or_wget)" = "x" ]; then \ + echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \ + else \ + echo "Downloading $(nnuedownloadurl)"; $(curl_or_wget) $(nnuedownloadurl) > $(nnuenet);\ + fi; \ + fi; + $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) + @if [ "x$(shasum_command)" != "x" ]; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Failed download or $(nnuenet) corrupted, please delete!"; exit 1; \ + fi \ + else \ + echo "shasum / sha256sum not found, skipping net validation"; \ + fi + +# clean binaries and objects +objclean: + @rm -f $(EXE) *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o + +# clean auxiliary profiling files +profileclean: + @rm -rf profdir + @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s + @rm -f stockfish.profdata *.profraw + +default: + help + +### ========================================================================== +### Section 5. Private Targets +### ========================================================================== + +all: $(EXE) .depend + +config-sanity: + @echo "" + @echo "Config:" + @echo "debug: '$(debug)'" + @echo "sanitize: '$(sanitize)'" + @echo "optimize: '$(optimize)'" + @echo "arch: '$(arch)'" + @echo "bits: '$(bits)'" + @echo "kernel: '$(KERNEL)'" + @echo "os: '$(OS)'" + @echo "prefetch: '$(prefetch)'" + @echo "popcnt: '$(popcnt)'" + @echo "pext: '$(pext)'" + @echo "sse: '$(sse)'" + @echo "mmx: '$(mmx)'" + @echo "sse2: '$(sse2)'" + @echo "ssse3: '$(ssse3)'" + @echo "sse41: '$(sse41)'" + @echo "avx2: '$(avx2)'" + @echo "avx512: '$(avx512)'" + @echo "vnni256: '$(vnni256)'" + @echo "vnni512: '$(vnni512)'" + @echo "neon: '$(neon)'" + @echo "" + @echo "Flags:" + @echo "CXX: $(CXX)" + @echo "CXXFLAGS: $(CXXFLAGS)" + @echo "LDFLAGS: $(LDFLAGS)" + @echo "" + @echo "Testing config sanity. If this fails, try 'make help' ..." + @echo "" + @test "$(debug)" = "yes" || test "$(debug)" = "no" + @test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no" + @test "$(optimize)" = "yes" || test "$(optimize)" = "no" + @test "$(SUPPORTED_ARCH)" = "true" + @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ + test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || \ + test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" + @test "$(bits)" = "32" || test "$(bits)" = "64" + @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" + @test "$(popcnt)" = "yes" || test "$(popcnt)" = "no" + @test "$(pext)" = "yes" || test "$(pext)" = "no" + @test "$(sse)" = "yes" || test "$(sse)" = "no" + @test "$(mmx)" = "yes" || test "$(mmx)" = "no" + @test "$(sse2)" = "yes" || test "$(sse2)" = "no" + @test "$(ssse3)" = "yes" || test "$(ssse3)" = "no" + @test "$(sse41)" = "yes" || test "$(sse41)" = "no" + @test "$(avx2)" = "yes" || test "$(avx2)" = "no" + @test "$(avx512)" = "yes" || test "$(avx512)" = "no" + @test "$(vnni256)" = "yes" || test "$(vnni256)" = "no" + @test "$(vnni512)" = "yes" || test "$(vnni512)" = "no" + @test "$(neon)" = "yes" || test "$(neon)" = "no" + @test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \ + || test "$(comp)" = "armv7a-linux-androideabi16-clang" || test "$(comp)" = "aarch64-linux-android21-clang" + +$(EXE): $(OBJS) + +$(CXX) -o $@ $(OBJS) $(LDFLAGS) + +clang-profile-make: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-generate ' \ + EXTRALDFLAGS=' -fprofile-instr-generate' \ + all + +clang-profile-use: + $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \ + EXTRALDFLAGS='-fprofile-use ' \ + all + +gcc-profile-make: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-generate' \ + EXTRALDFLAGS='-lgcov' \ + all + +gcc-profile-use: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-use -fno-peel-loops -fno-tracer' \ + EXTRALDFLAGS='-lgcov' \ + all + +icc-profile-make: + @mkdir -p profdir + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \ + all + +icc-profile-use: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \ + all + +.depend: + -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null + +-include .depend diff --git a/engines/stockfish13/benchmark.cpp b/engines/stockfish13/benchmark.cpp new file mode 100644 index 0000000..5a713a2 --- /dev/null +++ b/engines/stockfish13/benchmark.cpp @@ -0,0 +1,170 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "position.h" + +using namespace std; + +namespace { + +const vector Defaults = { + "setoption name UCI_Chess960 value false", + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", + "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11", + "4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19", + "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6", + "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4", + "r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15", + "r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13", + "r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16", + "4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17", + "2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11", + "r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16", + "3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22", + "r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18", + "4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22", + "3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26", + "6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1", + "3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1", + "2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3", + "8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1", + "7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1", + "8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1", + "8/1p3pp1/7p/5P1P/2k3P1/8/2K2P2/8 w - - 0 1", + "8/pp2r1k1/2p1p3/3pP2p/1P1P1P1P/P5KR/8/8 w - - 0 1", + "8/3p4/p1bk3p/Pp6/1Kp1PpPp/2P2P1P/2P5/5B2 b - - 0 1", + "5k2/7R/4P2p/5K2/p1r2P1p/8/8/8 b - - 0 1", + "6k1/6p1/P6p/r1N5/5p2/7P/1b3PP1/4R1K1 w - - 0 1", + "1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1", + "6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1", + "8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1", + "5rk1/q6p/2p3bR/1pPp1rP1/1P1Pp3/P3B1Q1/1K3P2/R7 w - - 93 90", + "4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21", + "r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16", + "3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40", + "4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1", + + // 5-man positions + "8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate + "8/8/8/5N2/8/p7/8/2NK3k w - - 0 1", // Na2 - mate + "8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1", // draw + + // 6-man positions + "8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1", // Re5 - mate + "8/2p4P/8/kr6/6R1/8/8/1K6 w - - 0 1", // Ka2 - mate + "8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1", // Nd2 - draw + + // 7-man positions + "8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124", // Draw + + // Mate and stalemate positions + "6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1", + "r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1", + "8/8/8/8/8/6k1/6p1/6K1 w - -", + "7k/7P/6K1/8/3B4/8/8/8 b - -", + + // Chess 960 + "setoption name UCI_Chess960 value true", + "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6", + "setoption name UCI_Chess960 value false" +}; + +} // namespace + +/// setup_bench() builds a list of UCI commands to be run by bench. There +/// are five parameters: TT size in MB, number of search threads that +/// should be used, the limit value spent for each position, a file name +/// where to look for positions in FEN format, the type of the limit: +/// depth, perft, nodes and movetime (in millisecs), and evaluation type +/// mixed (default), classical, NNUE. +/// +/// bench -> search default positions up to depth 13 +/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB) +/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec +/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each +/// bench 16 1 5 default perft -> run a perft 5 on default positions + +vector setup_bench(const Position& current, istream& is) { + + vector fens, list; + string go, token; + + // Assign default values to missing arguments + string ttSize = (is >> token) ? token : "16"; + string threads = (is >> token) ? token : "1"; + string limit = (is >> token) ? token : "13"; + string fenFile = (is >> token) ? token : "default"; + string limitType = (is >> token) ? token : "depth"; + string evalType = (is >> token) ? token : "mixed"; + + go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; + + if (fenFile == "default") + fens = Defaults; + + else if (fenFile == "current") + fens.push_back(current.fen()); + + else + { + string fen; + ifstream file(fenFile); + + if (!file.is_open()) + { + cerr << "Unable to open file " << fenFile << endl; + exit(EXIT_FAILURE); + } + + while (getline(file, fen)) + if (!fen.empty()) + fens.push_back(fen); + + file.close(); + } + + list.emplace_back("setoption name Threads value " + threads); + list.emplace_back("setoption name Hash value " + ttSize); + list.emplace_back("ucinewgame"); + + size_t posCounter = 0; + + for (const string& fen : fens) + if (fen.find("setoption") != string::npos) + list.emplace_back(fen); + else + { + if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0)) + list.emplace_back("setoption name Use NNUE value 0"); + else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0)) + list.emplace_back("setoption name Use NNUE value 1"); + list.emplace_back("position fen " + fen); + list.emplace_back(go); + ++posCounter; + } + + list.emplace_back("setoption name Use NNUE value 1"); + + return list; +} diff --git a/engines/stockfish13/bitbase.cpp b/engines/stockfish13/bitbase.cpp new file mode 100644 index 0000000..bbe8e9a --- /dev/null +++ b/engines/stockfish13/bitbase.cpp @@ -0,0 +1,170 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "bitboard.h" +#include "types.h" + +namespace { + + // There are 24 possible pawn squares: files A to D and ranks from 2 to 7. + // Positions with the pawn on files E to H will be mirrored before probing. + constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608 + + std::bitset KPKBitbase; + + // A KPK bitbase index is an integer in [0, IndexMax] range + // + // Information is mapped in a way that minimizes the number of iterations: + // + // bit 0- 5: white king square (from SQ_A1 to SQ_H8) + // bit 6-11: black king square (from SQ_A1 to SQ_H8) + // bit 12: side to move (WHITE or BLACK) + // bit 13-14: white pawn file (from FILE_A to FILE_D) + // bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2) + unsigned index(Color stm, Square bksq, Square wksq, Square psq) { + return int(wksq) | (bksq << 6) | (stm << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15); + } + + enum Result { + INVALID = 0, + UNKNOWN = 1, + DRAW = 2, + WIN = 4 + }; + + Result& operator|=(Result& r, Result v) { return r = Result(r | v); } + + struct KPKPosition { + KPKPosition() = default; + explicit KPKPosition(unsigned idx); + operator Result() const { return result; } + Result classify(const std::vector& db); + + Color stm; + Square ksq[COLOR_NB], psq; + Result result; + }; + +} // namespace + + +bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) { + + assert(file_of(wpsq) <= FILE_D); + + return KPKBitbase[index(stm, bksq, wksq, wpsq)]; +} + + +void Bitbases::init() { + + std::vector db(MAX_INDEX); + unsigned idx, repeat = 1; + + // Initialize db with known win / draw positions + for (idx = 0; idx < MAX_INDEX; ++idx) + db[idx] = KPKPosition(idx); + + // Iterate through the positions until none of the unknown positions can be + // changed to either wins or draws (15 cycles needed). + while (repeat) + for (repeat = idx = 0; idx < MAX_INDEX; ++idx) + repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN); + + // Fill the bitbase with the decisive results + for (idx = 0; idx < MAX_INDEX; ++idx) + if (db[idx] == WIN) + KPKBitbase.set(idx); +} + + +namespace { + + KPKPosition::KPKPosition(unsigned idx) { + + ksq[WHITE] = Square((idx >> 0) & 0x3F); + ksq[BLACK] = Square((idx >> 6) & 0x3F); + stm = Color ((idx >> 12) & 0x01); + psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7))); + + // Invalid if two pieces are on the same square or if a king can be captured + if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 + || ksq[WHITE] == psq + || ksq[BLACK] == psq + || (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK]))) + result = INVALID; + + // Win if the pawn can be promoted without getting captured + else if ( stm == WHITE + && rank_of(psq) == RANK_7 + && ksq[WHITE] != psq + NORTH + && ( distance(ksq[BLACK], psq + NORTH) > 1 + || (distance(ksq[WHITE], psq + NORTH) == 1))) + result = WIN; + + // Draw if it is stalemate or the black king can capture the pawn + else if ( stm == BLACK + && ( !(attacks_bb(ksq[BLACK]) & ~(attacks_bb(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq))) + || (attacks_bb(ksq[BLACK]) & ~attacks_bb(ksq[WHITE]) & psq))) + result = DRAW; + + // Position will be classified later + else + result = UNKNOWN; + } + + Result KPKPosition::classify(const std::vector& db) { + + // White to move: If one move leads to a position classified as WIN, the result + // of the current position is WIN. If all moves lead to positions classified + // as DRAW, the current position is classified as DRAW, otherwise the current + // position is classified as UNKNOWN. + // + // Black to move: If one move leads to a position classified as DRAW, the result + // of the current position is DRAW. If all moves lead to positions classified + // as WIN, the position is classified as WIN, otherwise the current position is + // classified as UNKNOWN. + const Result Good = (stm == WHITE ? WIN : DRAW); + const Result Bad = (stm == WHITE ? DRAW : WIN); + + Result r = INVALID; + Bitboard b = attacks_bb(ksq[stm]); + + while (b) + r |= stm == WHITE ? db[index(BLACK, ksq[BLACK] , pop_lsb(&b), psq)] + : db[index(WHITE, pop_lsb(&b), ksq[WHITE], psq)]; + + if (stm == WHITE) + { + if (rank_of(psq) < RANK_7) // Single push + r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)]; + + if ( rank_of(psq) == RANK_2 // Double push + && psq + NORTH != ksq[WHITE] + && psq + NORTH != ksq[BLACK]) + r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)]; + } + + return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad; + } + +} // namespace diff --git a/engines/stockfish13/bitboard.cpp b/engines/stockfish13/bitboard.cpp new file mode 100644 index 0000000..80206b5 --- /dev/null +++ b/engines/stockfish13/bitboard.cpp @@ -0,0 +1,213 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "bitboard.h" +#include "misc.h" + +uint8_t PopCnt16[1 << 16]; +uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; + +Bitboard SquareBB[SQUARE_NB]; +Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; +Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; + +Magic RookMagics[SQUARE_NB]; +Magic BishopMagics[SQUARE_NB]; + +namespace { + + Bitboard RookTable[0x19000]; // To store rook attacks + Bitboard BishopTable[0x1480]; // To store bishop attacks + + void init_magics(PieceType pt, Bitboard table[], Magic magics[]); + +} + + +/// safe_destination() returns the bitboard of target square for the given step +/// from the given square. If the step is off the board, returns empty bitboard. + +inline Bitboard safe_destination(Square s, int step) { + Square to = Square(s + step); + return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0); +} + + +/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable +/// to be printed to standard output. Useful for debugging. + +const std::string Bitboards::pretty(Bitboard b) { + + std::string s = "+---+---+---+---+---+---+---+---+\n"; + + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + s += b & make_square(f, r) ? "| X " : "| "; + + s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n"; + } + s += " a b c d e f g h\n"; + + return s; +} + + +/// Bitboards::init() initializes various bitboard tables. It is called at +/// startup and relies on global objects to be already zero-initialized. + +void Bitboards::init() { + + for (unsigned i = 0; i < (1 << 16); ++i) + PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); + + for (Square s = SQ_A1; s <= SQ_H8; ++s) + SquareBB[s] = (1ULL << s); + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); + + init_magics(ROOK, RookTable, RookMagics); + init_magics(BISHOP, BishopTable, BishopMagics); + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); + PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); + + for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} ) + PseudoAttacks[KING][s1] |= safe_destination(s1, step); + + for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} ) + PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step); + + PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); + PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0); + + for (PieceType pt : { BISHOP, ROOK }) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + if (PseudoAttacks[pt][s1] & s2) + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + } +} + + +namespace { + + Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { + + Bitboard attacks = 0; + Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; + Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; + + for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) + { + Square s = sq; + while(safe_destination(s, d) && !(occupied & s)) + attacks |= (s += d); + } + + return attacks; + } + + + // init_magics() computes all rook and bishop attacks at startup. Magic + // bitboards are used to look up attacks of sliding pieces. As a reference see + // www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so + // called "fancy" approach. + + void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { + + // Optimal PRNG seeds to pick the correct magics in the shortest time + int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, + { 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } }; + + Bitboard occupancy[4096], reference[4096], edges, b; + int epoch[4096] = {}, cnt = 0, size = 0; + + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + // Board edges are not considered in the relevant occupancies + edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s)); + + // Given a square 's', the mask is the bitboard of sliding attacks from + // 's' computed on an empty board. The index must be big enough to contain + // all the attacks for each possible subset of the mask and so is 2 power + // the number of 1s of the mask. Hence we deduce the size of the shift to + // apply to the 64 or 32 bits word to get the index. + Magic& m = magics[s]; + m.mask = sliding_attack(pt, s, 0) & ~edges; + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); + + // Set the offset for the attacks table of the square. We have individual + // table sizes for each square with "Fancy Magic Bitboards". + m.attacks = s == SQ_A1 ? table : magics[s - 1].attacks + size; + + // Use Carry-Rippler trick to enumerate all subsets of masks[s] and + // store the corresponding sliding attack bitboard in reference[]. + b = size = 0; + do { + occupancy[size] = b; + reference[size] = sliding_attack(pt, s, b); + + if (HasPext) + m.attacks[pext(b, m.mask)] = reference[size]; + + size++; + b = (b - m.mask) & m.mask; + } while (b); + + if (HasPext) + continue; + + PRNG rng(seeds[Is64Bit][rank_of(s)]); + + // Find a magic for square 's' picking up an (almost) random number + // until we find the one that passes the verification test. + for (int i = 0; i < size; ) + { + for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; ) + m.magic = rng.sparse_rand(); + + // A good magic must map every possible occupancy to an index that + // looks up the correct sliding attack in the attacks[s] database. + // Note that we build up the database for square 's' as a side + // effect of verifying the magic. Keep track of the attempt count + // and save it in epoch[], little speed-up trick to avoid resetting + // m.attacks[] after every failed attempt. + for (++cnt, i = 0; i < size; ++i) + { + unsigned idx = m.index(occupancy[i]); + + if (epoch[idx] < cnt) + { + epoch[idx] = cnt; + m.attacks[idx] = reference[i]; + } + else if (m.attacks[idx] != reference[i]) + break; + } + } + } + } +} diff --git a/engines/stockfish13/bitboard.h b/engines/stockfish13/bitboard.h new file mode 100644 index 0000000..29d8f66 --- /dev/null +++ b/engines/stockfish13/bitboard.h @@ -0,0 +1,433 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef BITBOARD_H_INCLUDED +#define BITBOARD_H_INCLUDED + +#include + +#include "types.h" + +namespace Bitbases { + +void init(); +bool probe(Square wksq, Square wpsq, Square bksq, Color us); + +} + +namespace Bitboards { + +void init(); +const std::string pretty(Bitboard b); + +} + +constexpr Bitboard AllSquares = ~Bitboard(0); +constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; + +constexpr Bitboard FileABB = 0x0101010101010101ULL; +constexpr Bitboard FileBBB = FileABB << 1; +constexpr Bitboard FileCBB = FileABB << 2; +constexpr Bitboard FileDBB = FileABB << 3; +constexpr Bitboard FileEBB = FileABB << 4; +constexpr Bitboard FileFBB = FileABB << 5; +constexpr Bitboard FileGBB = FileABB << 6; +constexpr Bitboard FileHBB = FileABB << 7; + +constexpr Bitboard Rank1BB = 0xFF; +constexpr Bitboard Rank2BB = Rank1BB << (8 * 1); +constexpr Bitboard Rank3BB = Rank1BB << (8 * 2); +constexpr Bitboard Rank4BB = Rank1BB << (8 * 3); +constexpr Bitboard Rank5BB = Rank1BB << (8 * 4); +constexpr Bitboard Rank6BB = Rank1BB << (8 * 5); +constexpr Bitboard Rank7BB = Rank1BB << (8 * 6); +constexpr Bitboard Rank8BB = Rank1BB << (8 * 7); + +constexpr Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; +constexpr Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; +constexpr Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; +constexpr Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB); + +constexpr Bitboard KingFlank[FILE_NB] = { + QueenSide ^ FileDBB, QueenSide, QueenSide, + CenterFiles, CenterFiles, + KingSide, KingSide, KingSide ^ FileEBB +}; + +extern uint8_t PopCnt16[1 << 16]; +extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; + +extern Bitboard SquareBB[SQUARE_NB]; +extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; + + +/// Magic holds all magic bitboards relevant data for a single square +struct Magic { + Bitboard mask; + Bitboard magic; + Bitboard* attacks; + unsigned shift; + + // Compute the attack's index using the 'magic bitboards' approach + unsigned index(Bitboard occupied) const { + + if (HasPext) + return unsigned(pext(occupied, mask)); + + if (Is64Bit) + return unsigned(((occupied & mask) * magic) >> shift); + + unsigned lo = unsigned(occupied) & unsigned(mask); + unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); + return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; + } +}; + +extern Magic RookMagics[SQUARE_NB]; +extern Magic BishopMagics[SQUARE_NB]; + +inline Bitboard square_bb(Square s) { + assert(is_ok(s)); + return SquareBB[s]; +} + + +/// Overloads of bitwise operators between a Bitboard and a Square for testing +/// whether a given bit is set in a bitboard, and for setting and clearing bits. + +inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); } +inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); } +inline Bitboard operator^( Bitboard b, Square s) { return b ^ square_bb(s); } +inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } +inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } + +inline Bitboard operator&(Square s, Bitboard b) { return b & s; } +inline Bitboard operator|(Square s, Bitboard b) { return b | s; } +inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } + +inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } + +constexpr bool more_than_one(Bitboard b) { + return b & (b - 1); +} + + +constexpr bool opposite_colors(Square s1, Square s2) { + return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1; +} + + +/// rank_bb() and file_bb() return a bitboard representing all the squares on +/// the given file or rank. + +constexpr Bitboard rank_bb(Rank r) { + return Rank1BB << (8 * r); +} + +constexpr Bitboard rank_bb(Square s) { + return rank_bb(rank_of(s)); +} + +constexpr Bitboard file_bb(File f) { + return FileABB << f; +} + +constexpr Bitboard file_bb(Square s) { + return file_bb(file_of(s)); +} + + +/// shift() moves a bitboard one or two steps as specified by the direction D + +template +constexpr Bitboard shift(Bitboard b) { + return D == NORTH ? b << 8 : D == SOUTH ? b >> 8 + : D == NORTH+NORTH? b <<16 : D == SOUTH+SOUTH? b >>16 + : D == EAST ? (b & ~FileHBB) << 1 : D == WEST ? (b & ~FileABB) >> 1 + : D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7 + : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9 + : 0; +} + + +/// pawn_attacks_bb() returns the squares attacked by pawns of the given color +/// from the squares in the given bitboard. + +template +constexpr Bitboard pawn_attacks_bb(Bitboard b) { + return C == WHITE ? shift(b) | shift(b) + : shift(b) | shift(b); +} + +inline Bitboard pawn_attacks_bb(Color c, Square s) { + + assert(is_ok(s)); + return PawnAttacks[c][s]; +} + + +/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the +/// given color from the squares in the given bitboard. + +template +constexpr Bitboard pawn_double_attacks_bb(Bitboard b) { + return C == WHITE ? shift(b) & shift(b) + : shift(b) & shift(b); +} + + +/// adjacent_files_bb() returns a bitboard representing all the squares on the +/// adjacent files of a given square. + +constexpr Bitboard adjacent_files_bb(Square s) { + return shift(file_bb(s)) | shift(file_bb(s)); +} + + +/// line_bb() returns a bitboard representing an entire line (from board edge +/// to board edge) that intersects the two given squares. If the given squares +/// are not on a same file/rank/diagonal, the function returns 0. For instance, +/// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. + +inline Bitboard line_bb(Square s1, Square s2) { + + assert(is_ok(s1) && is_ok(s2)); + return LineBB[s1][s2]; +} + + +/// between_bb() returns a bitboard representing squares that are linearly +/// between the two given squares (excluding the given squares). If the given +/// squares are not on a same file/rank/diagonal, we return 0. For instance, +/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5 and E6. + +inline Bitboard between_bb(Square s1, Square s2) { + Bitboard b = line_bb(s1, s2) & ((AllSquares << s1) ^ (AllSquares << s2)); + return b & (b - 1); //exclude lsb +} + + +/// forward_ranks_bb() returns a bitboard representing the squares on the ranks +/// in front of the given one, from the point of view of the given color. For instance, +/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. + +constexpr Bitboard forward_ranks_bb(Color c, Square s) { + return c == WHITE ? ~Rank1BB << 8 * relative_rank(WHITE, s) + : ~Rank8BB >> 8 * relative_rank(BLACK, s); +} + + +/// forward_file_bb() returns a bitboard representing all the squares along the +/// line in front of the given one, from the point of view of the given color. + +constexpr Bitboard forward_file_bb(Color c, Square s) { + return forward_ranks_bb(c, s) & file_bb(s); +} + + +/// pawn_attack_span() returns a bitboard representing all the squares that can +/// be attacked by a pawn of the given color when it moves along its file, starting +/// from the given square. + +constexpr Bitboard pawn_attack_span(Color c, Square s) { + return forward_ranks_bb(c, s) & adjacent_files_bb(s); +} + + +/// passed_pawn_span() returns a bitboard which can be used to test if a pawn of +/// the given color and on the given square is a passed pawn. + +constexpr Bitboard passed_pawn_span(Color c, Square s) { + return pawn_attack_span(c, s) | forward_file_bb(c, s); +} + + +/// aligned() returns true if the squares s1, s2 and s3 are aligned either on a +/// straight or on a diagonal line. + +inline bool aligned(Square s1, Square s2, Square s3) { + return line_bb(s1, s2) & s3; +} + + +/// distance() functions return the distance between x and y, defined as the +/// number of steps for a king in x to reach y. + +template inline int distance(Square x, Square y); +template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } +template<> inline int distance(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); } +template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; } + +inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } +inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); } + + +/// attacks_bb(Square) returns the pseudo attacks of the give piece type +/// assuming an empty board. + +template +inline Bitboard attacks_bb(Square s) { + + assert((Pt != PAWN) && (is_ok(s))); + + return PseudoAttacks[Pt][s]; +} + + +/// attacks_bb(Square, Bitboard) returns the attacks by the given piece +/// assuming the board is occupied according to the passed Bitboard. +/// Sliding piece attacks do not continue passed an occupied square. + +template +inline Bitboard attacks_bb(Square s, Bitboard occupied) { + + assert((Pt != PAWN) && (is_ok(s))); + + switch (Pt) + { + case BISHOP: return BishopMagics[s].attacks[BishopMagics[s].index(occupied)]; + case ROOK : return RookMagics[s].attacks[ RookMagics[s].index(occupied)]; + case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : return PseudoAttacks[Pt][s]; + } +} + +inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { + + assert((pt != PAWN) && (is_ok(s))); + + switch (pt) + { + case BISHOP: return attacks_bb(s, occupied); + case ROOK : return attacks_bb< ROOK>(s, occupied); + case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : return PseudoAttacks[pt][s]; + } +} + + +/// popcount() counts the number of non-zero bits in a bitboard + +inline int popcount(Bitboard b) { + +#ifndef USE_POPCNT + + union { Bitboard bb; uint16_t u[4]; } v = { b }; + return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]]; + +#elif defined(_MSC_VER) || defined(__INTEL_COMPILER) + + return (int)_mm_popcnt_u64(b); + +#else // Assumed gcc or compatible compiler + + return __builtin_popcountll(b); + +#endif +} + + +/// lsb() and msb() return the least/most significant bit in a non-zero bitboard + +#if defined(__GNUC__) // GCC, Clang, ICC + +inline Square lsb(Bitboard b) { + assert(b); + return Square(__builtin_ctzll(b)); +} + +inline Square msb(Bitboard b) { + assert(b); + return Square(63 ^ __builtin_clzll(b)); +} + +#elif defined(_MSC_VER) // MSVC + +#ifdef _WIN64 // MSVC, WIN64 + +inline Square lsb(Bitboard b) { + assert(b); + unsigned long idx; + _BitScanForward64(&idx, b); + return (Square) idx; +} + +inline Square msb(Bitboard b) { + assert(b); + unsigned long idx; + _BitScanReverse64(&idx, b); + return (Square) idx; +} + +#else // MSVC, WIN32 + +inline Square lsb(Bitboard b) { + assert(b); + unsigned long idx; + + if (b & 0xffffffff) { + _BitScanForward(&idx, int32_t(b)); + return Square(idx); + } else { + _BitScanForward(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } +} + +inline Square msb(Bitboard b) { + assert(b); + unsigned long idx; + + if (b >> 32) { + _BitScanReverse(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } else { + _BitScanReverse(&idx, int32_t(b)); + return Square(idx); + } +} + +#endif + +#else // Compiler is neither GCC nor MSVC compatible + +#error "Compiler not supported." + +#endif + + +/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard + +inline Square pop_lsb(Bitboard* b) { + assert(*b); + const Square s = lsb(*b); + *b &= *b - 1; + return s; +} + + +/// frontmost_sq() returns the most advanced square for the given color, +/// requires a non-zero bitboard. +inline Square frontmost_sq(Color c, Bitboard b) { + assert(b); + return c == WHITE ? msb(b) : lsb(b); +} + +#endif // #ifndef BITBOARD_H_INCLUDED diff --git a/engines/stockfish13/endgame.cpp b/engines/stockfish13/endgame.cpp new file mode 100644 index 0000000..c8be219 --- /dev/null +++ b/engines/stockfish13/endgame.cpp @@ -0,0 +1,743 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitboard.h" +#include "endgame.h" +#include "movegen.h" + +namespace { + + // Used to drive the king towards the edge of the board + // in KX vs K and KQ vs KR endgames. + // Values range from 27 (center squares) to 90 (in the corners) + inline int push_to_edge(Square s) { + int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s)); + return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2); + } + + // Used to drive the king towards A1H8 corners in KBN vs K endgames. + // Values range from 0 on A8H1 diagonal to 7 in A1H8 corners + inline int push_to_corner(Square s) { + return abs(7 - rank_of(s) - file_of(s)); + } + + // Drive a piece close to or away from another piece + inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); } + inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); } + +#ifndef NDEBUG + bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) { + return pos.non_pawn_material(c) == npm && pos.count(c) == pawnsCnt; + } +#endif + + // Map the square as if strongSide is white and strongSide's only pawn + // is on the left half of the board. + Square normalize(const Position& pos, Color strongSide, Square sq) { + + assert(pos.count(strongSide) == 1); + + if (file_of(pos.square(strongSide)) >= FILE_E) + sq = flip_file(sq); + + return strongSide == WHITE ? sq : flip_rank(sq); + } + +} // namespace + + +namespace Endgames { + + std::pair, Map> maps; + + void init() { + + add("KPK"); + add("KNNK"); + add("KBNK"); + add("KRKP"); + add("KRKB"); + add("KRKN"); + add("KQKP"); + add("KQKR"); + add("KNNKP"); + + add("KRPKR"); + add("KRPKB"); + add("KBPKB"); + add("KBPKN"); + add("KBPPKB"); + add("KRPPKRP"); + } +} + + +/// Mate with KX vs K. This function is used to evaluate positions with +/// king and plenty of material vs a lone king. It simply gives the +/// attacking side a bonus for driving the defending king towards the edge +/// of the board, and for keeping the distance between the two kings small. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + assert(!pos.checkers()); // Eval is never called when in check + + // Stalemate detection with lone king + if (pos.side_to_move() == weakSide && !MoveList(pos).size()) + return VALUE_DRAW; + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + push_to_edge(weakKing) + + push_close(strongKing, weakKing); + + if ( pos.count(strongSide) + || pos.count(strongSide) + ||(pos.count(strongSide) && pos.count(strongSide)) + || ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares) + && (pos.pieces(strongSide, BISHOP) & DarkSquares))) + result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the +/// defending king towards a corner square that our bishop attacks. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square strongKing = pos.square(strongSide); + Square strongBishop = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + // If our bishop does not attack A1/H8, we flip the enemy king square + // to drive to opposite corners (A8/H1). + + Value result = (VALUE_KNOWN_WIN + 3520) + + push_close(strongKing, weakKing) + + 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing); + + assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY); + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KP vs K. This endgame is evaluated with the help of a bitbase +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, VALUE_ZERO, 1)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + // Assume strongSide is white and the pawn is on files A-D + Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); + Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); + Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); + + Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; + + if (!Bitbases::probe(strongKing, strongPawn, weakKing, us)) + return VALUE_DRAW; + + Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn)); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KP. This is a somewhat tricky endgame to evaluate precisely without +/// a bitbase. The function below returns drawish scores when the pawn is +/// far advanced with support of the king, while the attacking king is far +/// away. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongRook = pos.square(strongSide); + Square weakPawn = pos.square(weakSide); + Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8)); + Value result; + + // If the stronger side's king is in front of the pawn, it's a win + if (forward_file_bb(strongSide, strongKing) & weakPawn) + result = RookValueEg - distance(strongKing, weakPawn); + + // If the weaker side's king is too far from the pawn and the rook, + // it's a win. + else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide) + && distance(weakKing, strongRook) >= 3) + result = RookValueEg - distance(strongKing, weakPawn); + + // If the pawn is far advanced and supported by the defending king, + // the position is drawish + else if ( relative_rank(strongSide, weakKing) <= RANK_3 + && distance(weakKing, weakPawn) == 1 + && relative_rank(strongSide, strongKing) >= RANK_4 + && distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide)) + result = Value(80) - 8 * distance(strongKing, weakPawn); + + else + result = Value(200) - 8 * ( distance(strongKing, weakPawn + pawn_push(weakSide)) + - distance(weakKing, weakPawn + pawn_push(weakSide)) + - distance(weakPawn, queeningSquare)); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KB. This is very simple, and always returns drawish scores. The +/// score is slightly bigger when the defending king is close to the edge. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 0)); + assert(verify_material(pos, weakSide, BishopValueMg, 0)); + + Value result = Value(push_to_edge(pos.square(weakSide))); + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KN. The attacking side has slightly better winning chances than +/// in KR vs KB, particularly if the king and the knight are far apart. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 0)); + assert(verify_material(pos, weakSide, KnightValueMg, 0)); + + Square weakKing = pos.square(weakSide); + Square weakKnight = pos.square(weakSide); + Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight)); + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KQ vs KP. In general, this is a win for the stronger side, but there are a +/// few important exceptions. A pawn on 7th rank and on the A,C,F or H files +/// with a king positioned next to it can be a draw, so in that case, we only +/// use the distance between the kings. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, QueenValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square weakPawn = pos.square(weakSide); + + Value result = Value(push_close(strongKing, weakKing)); + + if ( relative_rank(weakSide, weakPawn) != RANK_7 + || distance(weakKing, weakPawn) != 1 + || ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn)) + result += QueenValueEg - PawnValueEg; + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KQ vs KR. This is almost identical to KX vs K: we give the attacking +/// king a bonus for having the kings close together, and for forcing the +/// defending king towards the edge. If we also take care to avoid null move for +/// the defending side in the search, this is usually sufficient to win KQ vs KR. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, QueenValueMg, 0)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = QueenValueEg + - RookValueEg + + push_to_edge(weakKing) + + push_close(strongKing, weakKing); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KNN vs KP. Very drawish, but there are some mate opportunities if we can +/// press the weakSide King to a corner before the pawn advances too much. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); + + Square weakKing = pos.square(weakSide); + Square weakPawn = pos.square(weakSide); + + Value result = PawnValueEg + + 2 * push_to_edge(weakKing) + - 10 * relative_rank(weakSide, weakPawn); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Some cases of trivial draws +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + + +/// KB and one or more pawns vs K. It checks for draws with rook pawns and +/// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW +/// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling +/// will be used. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(pos.non_pawn_material(strongSide) == BishopValueMg); + assert(pos.count(strongSide) >= 1); + + // No assertions about the material of weakSide, because we want draws to + // be detected even when the weaker side has some pawns. + + Bitboard strongPawns = pos.pieces(strongSide, PAWN); + Bitboard allPawns = pos.pieces(PAWN); + + Square strongBishop = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongKing = pos.square(strongSide); + + // All strongSide pawns are on a single rook file? + if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB)) + { + Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8)); + + if ( opposite_colors(queeningSquare, strongBishop) + && distance(queeningSquare, weakKing) <= 1) + return SCALE_FACTOR_DRAW; + } + + // If all the pawns are on the same B or G file, then it's potentially a draw + if ((!(allPawns & ~FileBBB) || !(allPawns & ~FileGBB)) + && pos.non_pawn_material(weakSide) == 0 + && pos.count(weakSide) >= 1) + { + // Get the least advanced weakSide pawn + Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN)); + + // There's potential for a draw if our pawn is blocked on the 7th rank, + // the bishop cannot attack it or they only have one pawn left. + if ( relative_rank(strongSide, weakPawn) == RANK_7 + && (strongPawns & (weakPawn + pawn_push(weakSide))) + && (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns))) + { + int strongKingDist = distance(weakPawn, strongKing); + int weakKingDist = distance(weakPawn, weakKing); + + // It's a draw if the weak king is on its back two ranks, within 2 + // squares of the blocking pawn and the strong king is not + // closer. (I think this rule only fails in practically + // unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w + // and positions where qsearch will immediately correct the + // problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w). + if ( relative_rank(strongSide, weakKing) >= RANK_7 + && weakKingDist <= 2 + && weakKingDist <= strongKingDist) + return SCALE_FACTOR_DRAW; + } + } + + return SCALE_FACTOR_NONE; +} + + +/// KQ vs KR and one or more pawns. It tests for fortress draws with a rook on +/// the third rank defended by a pawn. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, QueenValueMg, 0)); + assert(pos.count(weakSide) == 1); + assert(pos.count(weakSide) >= 1); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square weakRook = pos.square(weakSide); + + if ( relative_rank(weakSide, weakKing) <= RANK_2 + && relative_rank(weakSide, strongKing) >= RANK_4 + && relative_rank(weakSide, weakRook) == RANK_3 + && ( pos.pieces(weakSide, PAWN) + & attacks_bb(weakKing) + & pawn_attacks_bb(strongSide, weakRook))) + return SCALE_FACTOR_DRAW; + + return SCALE_FACTOR_NONE; +} + + +/// KRP vs KR. This function knows a handful of the most important classes of +/// drawn positions, but is far from perfect. It would probably be a good idea +/// to add more knowledge in the future. +/// +/// It would also be nice to rewrite the actual code for this function, +/// which is mostly copied from Glaurung 1.x, and isn't very pretty. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 1)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + // Assume strongSide is white and the pawn is on files A-D + Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); + Square strongRook = normalize(pos, strongSide, pos.square(strongSide)); + Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); + Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); + Square weakRook = normalize(pos, strongSide, pos.square(weakSide)); + + File pawnFile = file_of(strongPawn); + Rank pawnRank = rank_of(strongPawn); + Square queeningSquare = make_square(pawnFile, RANK_8); + int tempo = (pos.side_to_move() == strongSide); + + // If the pawn is not too far advanced and the defending king defends the + // queening square, use the third-rank defence. + if ( pawnRank <= RANK_5 + && distance(weakKing, queeningSquare) <= 1 + && strongKing <= SQ_H5 + && (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6))) + return SCALE_FACTOR_DRAW; + + // The defending side saves a draw by checking from behind in case the pawn + // has advanced to the 6th rank with the king behind. + if ( pawnRank == RANK_6 + && distance(weakKing, queeningSquare) <= 1 + && rank_of(strongKing) + tempo <= RANK_6 + && (rank_of(weakRook) == RANK_1 || (!tempo && distance(weakRook, strongPawn) >= 3))) + return SCALE_FACTOR_DRAW; + + if ( pawnRank >= RANK_6 + && weakKing == queeningSquare + && rank_of(weakRook) == RANK_1 + && (!tempo || distance(strongKing, strongPawn) >= 2)) + return SCALE_FACTOR_DRAW; + + // White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7 + // and the black rook is behind the pawn. + if ( strongPawn == SQ_A7 + && strongRook == SQ_A8 + && (weakKing == SQ_H7 || weakKing == SQ_G7) + && file_of(weakRook) == FILE_A + && (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5)) + return SCALE_FACTOR_DRAW; + + // If the defending king blocks the pawn and the attacking king is too far + // away, it's a draw. + if ( pawnRank <= RANK_5 + && weakKing == strongPawn + NORTH + && distance(strongKing, strongPawn) - tempo >= 2 + && distance(strongKing, weakRook) - tempo >= 2) + return SCALE_FACTOR_DRAW; + + // Pawn on the 7th rank supported by the rook from behind usually wins if the + // attacking king is closer to the queening square than the defending king, + // and the defending king cannot gain tempi by threatening the attacking rook. + if ( pawnRank == RANK_7 + && pawnFile != FILE_A + && file_of(strongRook) == pawnFile + && strongRook != queeningSquare + && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo) + && (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo)) + return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare)); + + // Similar to the above, but with the pawn further back + if ( pawnFile != FILE_A + && file_of(strongRook) == pawnFile + && strongRook < strongPawn + && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo) + && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo) + && ( distance(weakKing, strongRook) + tempo >= 3 + || ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo + && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo)))) + return ScaleFactor( SCALE_FACTOR_MAX + - 8 * distance(strongPawn, queeningSquare) + - 2 * distance(strongKing, queeningSquare)); + + // If the pawn is not far advanced and the defending king is somewhere in + // the pawn's path, it's probably a draw. + if (pawnRank <= RANK_4 && weakKing > strongPawn) + { + if (file_of(weakKing) == file_of(strongPawn)) + return ScaleFactor(10); + if ( distance(weakKing, strongPawn) == 1 + && distance(strongKing, weakKing) > 2) + return ScaleFactor(24 - 2 * distance(strongKing, weakKing)); + } + return SCALE_FACTOR_NONE; +} + +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 1)); + assert(verify_material(pos, weakSide, BishopValueMg, 0)); + + // Test for a rook pawn + if (pos.pieces(PAWN) & (FileABB | FileHBB)) + { + Square weakKing = pos.square(weakSide); + Square weakBishop = pos.square(weakSide); + Square strongKing = pos.square(strongSide); + Square strongPawn = pos.square(strongSide); + Rank pawnRank = relative_rank(strongSide, strongPawn); + Direction push = pawn_push(strongSide); + + // If the pawn is on the 5th rank and the pawn (currently) is on + // the same color square as the bishop then there is a chance of + // a fortress. Depending on the king position give a moderate + // reduction or a stronger one if the defending king is near the + // corner but not trapped there. + if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn)) + { + int d = distance(strongPawn + 3 * push, weakKing); + + if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push)) + return ScaleFactor(24); + else + return ScaleFactor(48); + } + + // When the pawn has moved to the 6th rank we can be fairly sure + // it's drawn if the bishop attacks the square in front of the + // pawn from a reasonable distance and the defending king is near + // the corner + if ( pawnRank == RANK_6 + && distance(strongPawn + 2 * push, weakKing) <= 1 + && (attacks_bb(weakBishop) & (strongPawn + push)) + && distance(weakBishop, strongPawn) >= 2) + return ScaleFactor(8); + } + + return SCALE_FACTOR_NONE; +} + +/// KRPP vs KRP. There is just a single rule: if the stronger side has no passed +/// pawns and the defending king is actively placed, the position is drawish. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 2)); + assert(verify_material(pos, weakSide, RookValueMg, 1)); + + Square strongPawn1 = pos.squares(strongSide)[0]; + Square strongPawn2 = pos.squares(strongSide)[1]; + Square weakKing = pos.square(weakSide); + + // Does the stronger side have a passed pawn? + if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2)) + return SCALE_FACTOR_NONE; + + Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2)); + + if ( distance(weakKing, strongPawn1) <= 1 + && distance(weakKing, strongPawn2) <= 1 + && relative_rank(strongSide, weakKing) > pawnRank) + { + assert(pawnRank > RANK_1 && pawnRank < RANK_7); + return ScaleFactor(7 * pawnRank); + } + return SCALE_FACTOR_NONE; +} + + +/// K and two or more pawns vs K. There is just a single rule here: if all pawns +/// are on the same rook file and are blocked by the defending king, it's a draw. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(pos.non_pawn_material(strongSide) == VALUE_ZERO); + assert(pos.count(strongSide) >= 2); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square weakKing = pos.square(weakSide); + Bitboard strongPawns = pos.pieces(strongSide, PAWN); + + // If all pawns are ahead of the king on a single rook file, it's a draw. + if ( !(strongPawns & ~(FileABB | FileHBB)) + && !(strongPawns & ~passed_pawn_span(weakSide, weakKing))) + return SCALE_FACTOR_DRAW; + + return SCALE_FACTOR_NONE; +} + + +/// KBP vs KB. There are two rules: if the defending king is somewhere along the +/// path of the pawn, and the square of the king is not of the same color as the +/// stronger side's bishop, it's a draw. If the two bishops have opposite color, +/// it's almost always a draw. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, BishopValueMg, 1)); + assert(verify_material(pos, weakSide, BishopValueMg, 0)); + + Square strongPawn = pos.square(strongSide); + Square strongBishop = pos.square(strongSide); + Square weakBishop = pos.square(weakSide); + Square weakKing = pos.square(weakSide); + + // Case 1: Defending king blocks the pawn, and cannot be driven away + if ( (forward_file_bb(strongSide, strongPawn) & weakKing) + && ( opposite_colors(weakKing, strongBishop) + || relative_rank(strongSide, weakKing) <= RANK_6)) + return SCALE_FACTOR_DRAW; + + // Case 2: Opposite colored bishops + if (opposite_colors(strongBishop, weakBishop)) + return SCALE_FACTOR_DRAW; + + return SCALE_FACTOR_NONE; +} + + +/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, BishopValueMg, 2)); + assert(verify_material(pos, weakSide, BishopValueMg, 0)); + + Square strongBishop = pos.square(strongSide); + Square weakBishop = pos.square(weakSide); + + if (!opposite_colors(strongBishop, weakBishop)) + return SCALE_FACTOR_NONE; + + Square weakKing = pos.square(weakSide); + Square strongPawn1 = pos.squares(strongSide)[0]; + Square strongPawn2 = pos.squares(strongSide)[1]; + Square blockSq1, blockSq2; + + if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2)) + { + blockSq1 = strongPawn1 + pawn_push(strongSide); + blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1)); + } + else + { + blockSq1 = strongPawn2 + pawn_push(strongSide); + blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2)); + } + + switch (distance(strongPawn1, strongPawn2)) + { + case 0: + // Both pawns are on the same file. It's an easy draw if the defender firmly + // controls some square in the frontmost pawn's path. + if ( file_of(weakKing) == file_of(blockSq1) + && relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1) + && opposite_colors(weakKing, strongBishop)) + return SCALE_FACTOR_DRAW; + else + return SCALE_FACTOR_NONE; + + case 1: + // Pawns on adjacent files. It's a draw if the defender firmly controls the + // square in front of the frontmost pawn's path, and the square diagonally + // behind this square on the file of the other pawn. + if ( weakKing == blockSq1 + && opposite_colors(weakKing, strongBishop) + && ( weakBishop == blockSq2 + || (attacks_bb(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP)) + || distance(strongPawn1, strongPawn2) >= 2)) + return SCALE_FACTOR_DRAW; + + else if ( weakKing == blockSq2 + && opposite_colors(weakKing, strongBishop) + && ( weakBishop == blockSq1 + || (attacks_bb(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP)))) + return SCALE_FACTOR_DRAW; + else + return SCALE_FACTOR_NONE; + + default: + // The pawns are not on the same file or adjacent files. No scaling. + return SCALE_FACTOR_NONE; + } +} + + +/// KBP vs KN. There is a single rule: if the defending king is somewhere along +/// the path of the pawn, and the square of the king is not of the same color as +/// the stronger side's bishop, it's a draw. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, BishopValueMg, 1)); + assert(verify_material(pos, weakSide, KnightValueMg, 0)); + + Square strongPawn = pos.square(strongSide); + Square strongBishop = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + if ( file_of(weakKing) == file_of(strongPawn) + && relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing) + && ( opposite_colors(weakKing, strongBishop) + || relative_rank(strongSide, weakKing) <= RANK_6)) + return SCALE_FACTOR_DRAW; + + return SCALE_FACTOR_NONE; +} + + +/// KP vs KP. This is done by removing the weakest side's pawn and probing the +/// KP vs K bitbase: if the weakest side has a draw without the pawn, it probably +/// has at least a draw with the pawn as well. The exception is when the stronger +/// side's pawn is far advanced and not on a rook file; in this case it is often +/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1). +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, VALUE_ZERO, 1)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); + + // Assume strongSide is white and the pawn is on files A-D + Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); + Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); + Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); + + Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; + + // If the pawn has advanced to the fifth rank or further, and is not a + // rook pawn, it's too dangerous to assume that it's at least a draw. + if (rank_of(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A) + return SCALE_FACTOR_NONE; + + // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw, + // it's probably at least a draw even with the pawn. + return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; +} diff --git a/engines/stockfish13/endgame.h b/engines/stockfish13/endgame.h new file mode 100644 index 0000000..1351d88 --- /dev/null +++ b/engines/stockfish13/endgame.h @@ -0,0 +1,123 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef ENDGAME_H_INCLUDED +#define ENDGAME_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "position.h" +#include "types.h" + + +/// EndgameCode lists all supported endgame functions by corresponding codes + +enum EndgameCode { + + EVALUATION_FUNCTIONS, + KNNK, // KNN vs K + KNNKP, // KNN vs KP + KXK, // Generic "mate lone king" eval + KBNK, // KBN vs K + KPK, // KP vs K + KRKP, // KR vs KP + KRKB, // KR vs KB + KRKN, // KR vs KN + KQKP, // KQ vs KP + KQKR, // KQ vs KR + + SCALING_FUNCTIONS, + KBPsK, // KB and pawns vs K + KQKRPs, // KQ vs KR and pawns + KRPKR, // KRP vs KR + KRPKB, // KRP vs KB + KRPPKRP, // KRPP vs KRP + KPsK, // K and pawns vs K + KBPKB, // KBP vs KB + KBPPKB, // KBPP vs KB + KBPKN, // KBP vs KN + KPKP // KP vs KP +}; + + +/// Endgame functions can be of two types depending on whether they return a +/// Value or a ScaleFactor. + +template using +eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type; + + +/// Base and derived functors for endgame evaluation and scaling functions + +template +struct EndgameBase { + + explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {} + virtual ~EndgameBase() = default; + virtual T operator()(const Position&) const = 0; + + const Color strongSide, weakSide; +}; + + +template> +struct Endgame : public EndgameBase { + + explicit Endgame(Color c) : EndgameBase(c) {} + T operator()(const Position&) const override; +}; + + +/// The Endgames namespace handles the pointers to endgame evaluation and scaling +/// base objects in two std::map. We use polymorphism to invoke the actual +/// endgame function by calling its virtual operator(). + +namespace Endgames { + + template using Ptr = std::unique_ptr>; + template using Map = std::unordered_map>; + + extern std::pair, Map> maps; + + void init(); + + template + Map& map() { + return std::get::value>(maps); + } + + template> + void add(const std::string& code) { + + StateInfo st; + map()[Position().set(code, WHITE, &st).material_key()] = Ptr(new Endgame(WHITE)); + map()[Position().set(code, BLACK, &st).material_key()] = Ptr(new Endgame(BLACK)); + } + + template + const EndgameBase* probe(Key key) { + auto it = map().find(key); + return it != map().end() ? it->second.get() : nullptr; + } +} + +#endif // #ifndef ENDGAME_H_INCLUDED diff --git a/engines/stockfish13/evaluate.cpp b/engines/stockfish13/evaluate.cpp new file mode 100644 index 0000000..97fc70c --- /dev/null +++ b/engines/stockfish13/evaluate.cpp @@ -0,0 +1,1116 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include // For std::memset +#include +#include +#include +#include +#include +#include + +#include "bitboard.h" +#include "evaluate.h" +#include "material.h" +#include "misc.h" +#include "pawns.h" +#include "thread.h" +#include "uci.h" +#include "incbin/incbin.h" + + +// Macro to embed the default NNUE file data in the engine binary (using incbin.h, by Dale Weiler). +// This macro invocation will declare the following three variables +// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data +// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end +// const unsigned int gEmbeddedNNUESize; // the size of the embedded file +// Note that this does not work in Microsof Visual Studio. +#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) + INCBIN(EmbeddedNNUE, EvalFileDefaultName); +#else + const unsigned char gEmbeddedNNUEData[1] = {0x0}; + const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; + const unsigned int gEmbeddedNNUESize = 1; +#endif + + +using namespace std; +using namespace Eval::NNUE; + +namespace Eval { + + int useNNUE; + int64_t nnueHits; + int64_t evalHits; + + string eval_file_loaded = "None"; + + /// init_NNUE() tries to load a nnue network at startup time, or when the engine + /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" + /// The name of the nnue network is always retrieved from the EvalFile option. + /// We search the given network in three locations: internally (the default + /// network may be embedded in the binary), in the active working directory and + /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY + /// variable to have the engine search in a special directory in their distro. + + void init_NNUE() { + + useNNUE = Options["Use NNUE"]; + if (!useNNUE) + return; + + string eval_file = string(Options["EvalFile"]); + + #if defined(DEFAULT_NNUE_DIRECTORY) + #define stringify2(x) #x + #define stringify(x) stringify2(x) + vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; + #else + vector dirs = { "" , "" , CommandLine::binaryDirectory }; + #endif + + for (string directory : dirs) + if (eval_file_loaded != eval_file) + { + if (directory != "") + { + ifstream stream(directory + eval_file, ios::binary); + if (load_eval(eval_file, stream)) + eval_file_loaded = eval_file; + } + + if (directory == "" && eval_file == EvalFileDefaultName) + { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer : public basic_streambuf { + public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } + }; + + MemoryBuffer buffer(const_cast(reinterpret_cast(gEmbeddedNNUEData)), + size_t(gEmbeddedNNUESize)); + + istream stream(&buffer); + if (load_eval(eval_file, stream)) + eval_file_loaded = eval_file; + } + } + } + + /// verify_NNUE() verifies that the last net used was loaded successfully + void verify_NNUE() { + + string eval_file = string(Options["EvalFile"]); + + if (useNNUE && eval_file_loaded != eval_file) + { + UCI::OptionsMap defaults; + UCI::init(defaults); + + string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available."; + string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; + string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; + string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + string(defaults["EvalFile"]); + string msg5 = "The engine will be terminated now."; + + sync_cout << "info string ERROR: " << msg1 << sync_endl; + sync_cout << "info string ERROR: " << msg2 << sync_endl; + sync_cout << "info string ERROR: " << msg3 << sync_endl; + sync_cout << "info string ERROR: " << msg4 << sync_endl; + sync_cout << "info string ERROR: " << msg5 << sync_endl; + + exit(EXIT_FAILURE); + } + + if (useNNUE) + sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl; + else + sync_cout << "info string classical evaluation enabled" << sync_endl; + } +} + +namespace Trace { + + enum Tracing { NO_TRACE, TRACE }; + + enum Term { // The first 8 entries are reserved for PieceType + MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, WINNABLE, TOTAL, TERM_NB + }; + + Score scores[TERM_NB][COLOR_NB]; + + double to_cp(Value v) { return double(v) / PawnValueEg; } + + void add(int idx, Color c, Score s) { + scores[idx][c] = s; + } + + void add(int idx, Score w, Score b = SCORE_ZERO) { + scores[idx][WHITE] = w; + scores[idx][BLACK] = b; + } + + std::ostream& operator<<(std::ostream& os, Score s) { + os << std::setw(5) << to_cp(mg_value(s)) << " " + << std::setw(5) << to_cp(eg_value(s)); + return os; + } + + std::ostream& operator<<(std::ostream& os, Term t) { + + if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL) + os << " ---- ----" << " | " << " ---- ----"; + else + os << scores[t][WHITE] << " | " << scores[t][BLACK]; + + os << " | " << scores[t][WHITE] - scores[t][BLACK] << "\n"; + return os; + } +} + +using namespace Trace; + +namespace { + + // Threshold for lazy and space evaluation + constexpr Value LazyThreshold1 = Value(1400); + constexpr Value LazyThreshold2 = Value(1300); + constexpr Value SpaceThreshold = Value(12222); + constexpr Value NNUEThreshold1 = Value(550); + constexpr Value NNUEThreshold2 = Value(150); + + // KingAttackWeights[PieceType] contains king attack weights by piece type + constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 }; + + // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type, + // higher if multiple safe checks are possible for that piece type. + constexpr int SafeCheck[][2] = { + {}, {}, {792, 1283}, {645, 967}, {1084, 1897}, {772, 1119} + }; + +#define S(mg, eg) make_score(mg, eg) + + // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game, + // indexed by piece type and number of attacked squares in the mobility area. + constexpr Score MobilityBonus[][32] = { + { S(-62,-81), S(-53,-56), S(-12,-31), S( -4,-16), S( 3, 5), S( 13, 11), // Knight + S( 22, 17), S( 28, 20), S( 33, 25) }, + { S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishop + S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86), + S( 91, 88), S( 98, 97) }, + { S(-60,-78), S(-20,-17), S( 2, 23), S( 3, 39), S( 3, 70), S( 11, 99), // Rook + S( 22,103), S( 31,121), S( 40,134), S( 40,139), S( 41,158), S( 48,164), + S( 57,168), S( 57,169), S( 62,172) }, + { S(-30,-48), S(-12,-30), S( -8, -7), S( -9, 19), S( 20, 40), S( 23, 55), // Queen + S( 23, 59), S( 35, 75), S( 38, 78), S( 53, 96), S( 64, 96), S( 65,100), + S( 65,121), S( 66,127), S( 67,131), S( 67,133), S( 72,136), S( 72,141), + S( 77,147), S( 79,150), S( 93,151), S(108,168), S(108,168), S(108,171), + S(110,182), S(114,182), S(114,192), S(116,219) } + }; + + // KingProtector[knight/bishop] contains penalty for each distance unit to own king + constexpr Score KingProtector[] = { S(8, 9), S(6, 9) }; + + // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a + // pawn protected square on rank 4 to 6 which is also safe from a pawn attack. + constexpr Score Outpost[] = { S(56, 34), S(31, 23) }; + + // PassedRank[Rank] contains a bonus according to the rank of a passed pawn + constexpr Score PassedRank[RANK_NB] = { + S(0, 0), S(9, 28), S(15, 31), S(17, 39), S(64, 70), S(171, 177), S(277, 260) + }; + + // RookOnFile[semiopen/open] contains bonuses for each rook when there is + // no (friendly) pawn on the rook file. + constexpr Score RookOnFile[] = { S(19, 7), S(48, 27) }; + + // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to + // which piece type attacks which one. Attacks on lesser pieces which are + // pawn-defended are not considered. + constexpr Score ThreatByMinor[PIECE_TYPE_NB] = { + S(0, 0), S(5, 32), S(55, 41), S(77, 56), S(89, 119), S(79, 162) + }; + + constexpr Score ThreatByRook[PIECE_TYPE_NB] = { + S(0, 0), S(3, 44), S(37, 68), S(42, 60), S(0, 39), S(58, 43) + }; + + // Assorted bonuses and penalties + constexpr Score BadOutpost = S( -7, 36); + constexpr Score BishopOnKingRing = S( 24, 0); + constexpr Score BishopPawns = S( 3, 7); + constexpr Score BishopXRayPawns = S( 4, 5); + constexpr Score CorneredBishop = S( 50, 50); + constexpr Score FlankAttacks = S( 8, 0); + constexpr Score Hanging = S( 69, 36); + constexpr Score KnightOnQueen = S( 16, 11); + constexpr Score LongDiagonalBishop = S( 45, 0); + constexpr Score MinorBehindPawn = S( 18, 3); + constexpr Score PassedFile = S( 11, 8); + constexpr Score PawnlessFlank = S( 17, 95); + constexpr Score ReachableOutpost = S( 31, 22); + constexpr Score RestrictedPiece = S( 7, 7); + constexpr Score RookOnKingRing = S( 16, 0); + constexpr Score RookOnQueenFile = S( 6, 11); + constexpr Score SliderOnQueen = S( 60, 18); + constexpr Score ThreatByKing = S( 24, 89); + constexpr Score ThreatByPawnPush = S( 48, 39); + constexpr Score ThreatBySafePawn = S(173, 94); + constexpr Score TrappedRook = S( 55, 13); + constexpr Score WeakQueenProtection = S( 14, 0); + constexpr Score WeakQueen = S( 56, 15); + + +#undef S + + // Evaluation class computes and stores attacks tables and other working data + template + class Evaluation { + + public: + Evaluation() = delete; + explicit Evaluation(const Position& p) : pos(p) {} + Evaluation& operator=(const Evaluation&) = delete; + Value value(); + + private: + template void initialize(); + template Score pieces(); + template Score king() const; + template Score threats() const; + template Score passed() const; + template Score space() const; + Value winnable(Score score) const; + + const Position& pos; + Material::Entry* me; + Pawns::Entry* pe; + Bitboard mobilityArea[COLOR_NB]; + Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; + + // attackedBy[color][piece type] is a bitboard representing all squares + // attacked by a given color and piece type. Special "piece types" which + // is also calculated is ALL_PIECES. + Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; + + // attackedBy2[color] are the squares attacked by at least 2 units of a given + // color, including x-rays. But diagonal x-rays through pawns are not computed. + Bitboard attackedBy2[COLOR_NB]; + + // kingRing[color] are the squares adjacent to the king plus some other + // very near squares, depending on king position. + Bitboard kingRing[COLOR_NB]; + + // kingAttackersCount[color] is the number of pieces of the given color + // which attack a square in the kingRing of the enemy king. + int kingAttackersCount[COLOR_NB]; + + // kingAttackersWeight[color] is the sum of the "weights" of the pieces of + // the given color which attack a square in the kingRing of the enemy king. + // The weights of the individual piece types are given by the elements in + // the KingAttackWeights array. + int kingAttackersWeight[COLOR_NB]; + + // kingAttacksCount[color] is the number of attacks by the given color to + // squares directly adjacent to the enemy king. Pieces which attack more + // than one square are counted multiple times. For instance, if there is + // a white knight on g5 and black's king is on g8, this white knight adds 2 + // to kingAttacksCount[WHITE]. + int kingAttacksCount[COLOR_NB]; + }; + + + // Evaluation::initialize() computes king and pawn attacks, and the king ring + // bitboard for a given color. This is done at the beginning of the evaluation. + + template template + void Evaluation::initialize() { + + constexpr Color Them = ~Us; + constexpr Direction Up = pawn_push(Us); + constexpr Direction Down = -Up; + constexpr Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB : Rank7BB | Rank6BB); + + const Square ksq = pos.square(Us); + + Bitboard dblAttackByPawn = pawn_double_attacks_bb(pos.pieces(Us, PAWN)); + + // Find our pawns that are blocked or on the first two ranks + Bitboard b = pos.pieces(Us, PAWN) & (shift(pos.pieces()) | LowRanks); + + // Squares occupied by those pawns, by our king or queen, by blockers to attacks on our king + // or controlled by enemy pawns are excluded from the mobility area. + mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them)); + + // Initialize attackedBy[] for king and pawns + attackedBy[Us][KING] = attacks_bb(ksq); + attackedBy[Us][PAWN] = pe->pawn_attacks(Us); + attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN]; + attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]); + + // Init our king safety tables + Square s = make_square(std::clamp(file_of(ksq), FILE_B, FILE_G), + std::clamp(rank_of(ksq), RANK_2, RANK_7)); + kingRing[Us] = attacks_bb(s) | s; + + kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them)); + kingAttacksCount[Them] = kingAttackersWeight[Them] = 0; + + // Remove from kingRing[] the squares defended by two pawns + kingRing[Us] &= ~dblAttackByPawn; + } + + + // Evaluation::pieces() scores pieces of a given color and type + + template template + Score Evaluation::pieces() { + + constexpr Color Them = ~Us; + constexpr Direction Down = -pawn_push(Us); + constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB + : Rank5BB | Rank4BB | Rank3BB); + const Square* pl = pos.squares(Us); + + Bitboard b, bb; + Score score = SCORE_ZERO; + + attackedBy[Us][Pt] = 0; + + for (Square s = *pl; s != SQ_NONE; s = *++pl) + { + // Find attacked squares, including x-ray attacks for bishops and rooks + b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) + : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) + : attacks_bb(s, pos.pieces()); + + if (pos.blockers_for_king(Us) & s) + b &= line_bb(pos.square(Us), s); + + attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b; + attackedBy[Us][Pt] |= b; + attackedBy[Us][ALL_PIECES] |= b; + + if (b & kingRing[Them]) + { + kingAttackersCount[Us]++; + kingAttackersWeight[Us] += KingAttackWeights[Pt]; + kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]); + } + + else if (Pt == ROOK && (file_bb(s) & kingRing[Them])) + score += RookOnKingRing; + + else if (Pt == BISHOP && (attacks_bb(s, pos.pieces(PAWN)) & kingRing[Them])) + score += BishopOnKingRing; + + int mob = popcount(b & mobilityArea[Us]); + + mobility[Us] += MobilityBonus[Pt - 2][mob]; + + if (Pt == BISHOP || Pt == KNIGHT) + { + // Bonus if the piece is on an outpost square or can reach one + // Reduced bonus for knights (BadOutpost) if few relevant targets + bb = OutpostRanks & (attackedBy[Us][PAWN] | shift(pos.pieces(PAWN))) + & ~pe->pawn_attacks_span(Them); + Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN); + + if ( Pt == KNIGHT + && bb & s & ~CenterFiles // on a side outpost + && !(b & targets) // no relevant attacks + && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide)))) + score += BadOutpost; + else if (bb & s) + score += Outpost[Pt == BISHOP]; + else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) + score += ReachableOutpost; + + // Bonus for a knight or bishop shielded by pawn + if (shift(pos.pieces(PAWN)) & s) + score += MinorBehindPawn; + + // Penalty if the piece is far from the king + score -= KingProtector[Pt == BISHOP] * distance(pos.square(Us), s); + + if (Pt == BISHOP) + { + // Penalty according to the number of our pawns on the same color square as the + // bishop, bigger when the center files are blocked with pawns and smaller + // when the bishop is outside the pawn chain. + Bitboard blocked = pos.pieces(Us, PAWN) & shift(pos.pieces()); + + score -= BishopPawns * pos.pawns_on_same_color_squares(Us, s) + * (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles)); + + // Penalty for all enemy pawns x-rayed + score -= BishopXRayPawns * popcount(attacks_bb(s) & pos.pieces(Them, PAWN)); + + // Bonus for bishop on a long diagonal which can "see" both center squares + if (more_than_one(attacks_bb(s, pos.pieces(PAWN)) & Center)) + score += LongDiagonalBishop; + + // An important Chess960 pattern: a cornered bishop blocked by a friendly + // pawn diagonally in front of it is a very serious problem, especially + // when that pawn is also blocked. + if ( pos.is_chess960() + && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1))) + { + Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); + if (pos.piece_on(s + d) == make_piece(Us, PAWN)) + score -= !pos.empty(s + d + pawn_push(Us)) ? CorneredBishop * 4 + : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2 + : CorneredBishop; + } + } + } + + if (Pt == ROOK) + { + // Bonus for rook on the same file as a queen + if (file_bb(s) & pos.pieces(QUEEN)) + score += RookOnQueenFile; + + // Bonus for rook on an open or semi-open file + if (pos.is_on_semiopen_file(Us, s)) + score += RookOnFile[pos.is_on_semiopen_file(Them, s)]; + + // Penalty when trapped by the king, even more if the king cannot castle + else if (mob <= 3) + { + File kf = file_of(pos.square(Us)); + if ((kf < FILE_E) == (file_of(s) < kf)) + score -= TrappedRook * (1 + !pos.castling_rights(Us)); + } + } + + if (Pt == QUEEN) + { + // Penalty if any relative pin or discovered attack against the queen + Bitboard queenPinners; + if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners)) + score -= WeakQueen; + } + } + if (T) + Trace::add(Pt, Us, score); + + return score; + } + + + // Evaluation::king() assigns bonuses and penalties to a king of a given color + + template template + Score Evaluation::king() const { + + constexpr Color Them = ~Us; + constexpr Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB + : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB); + + Bitboard weak, b1, b2, b3, safe, unsafeChecks = 0; + Bitboard rookChecks, queenChecks, bishopChecks, knightChecks; + int kingDanger = 0; + const Square ksq = pos.square(Us); + + // Init the score with king shelter and enemy pawns storm + Score score = pe->king_safety(pos); + + // Attacked squares defended at most once by our queen or king + weak = attackedBy[Them][ALL_PIECES] + & ~attackedBy2[Us] + & (~attackedBy[Us][ALL_PIECES] | attackedBy[Us][KING] | attackedBy[Us][QUEEN]); + + // Analyse the safe enemy's checks which are possible on next move + safe = ~pos.pieces(Them); + safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]); + + b1 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); + b2 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); + + // Enemy rooks checks + rookChecks = b1 & attackedBy[Them][ROOK] & safe; + if (rookChecks) + kingDanger += SafeCheck[ROOK][more_than_one(rookChecks)]; + else + unsafeChecks |= b1 & attackedBy[Them][ROOK]; + + // Enemy queen safe checks: count them only if the checks are from squares from + // which opponent cannot give a rook check, because rook checks are more valuable. + queenChecks = (b1 | b2) & attackedBy[Them][QUEEN] & safe + & ~(attackedBy[Us][QUEEN] | rookChecks); + if (queenChecks) + kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)]; + + // Enemy bishops checks: count them only if they are from squares from which + // opponent cannot give a queen check, because queen checks are more valuable. + bishopChecks = b2 & attackedBy[Them][BISHOP] & safe + & ~queenChecks; + if (bishopChecks) + kingDanger += SafeCheck[BISHOP][more_than_one(bishopChecks)]; + + else + unsafeChecks |= b2 & attackedBy[Them][BISHOP]; + + // Enemy knights checks + knightChecks = attacks_bb(ksq) & attackedBy[Them][KNIGHT]; + if (knightChecks & safe) + kingDanger += SafeCheck[KNIGHT][more_than_one(knightChecks & safe)]; + else + unsafeChecks |= knightChecks; + + // Find the squares that opponent attacks in our king flank, the squares + // which they attack twice in that flank, and the squares that we defend. + b1 = attackedBy[Them][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp; + b2 = b1 & attackedBy2[Them]; + b3 = attackedBy[Us][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp; + + int kingFlankAttack = popcount(b1) + popcount(b2); + int kingFlankDefense = popcount(b3); + + kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] + + 185 * popcount(kingRing[Us] & weak) + + 148 * popcount(unsafeChecks) + + 98 * popcount(pos.blockers_for_king(Us)) + + 69 * kingAttacksCount[Them] + + 3 * kingFlankAttack * kingFlankAttack / 8 + + mg_value(mobility[Them] - mobility[Us]) + - 873 * !pos.count(Them) + - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) + - 6 * mg_value(score) / 8 + - 4 * kingFlankDefense + + 37; + + // Transform the kingDanger units into a Score, and subtract it from the evaluation + if (kingDanger > 100) + score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16); + + // Penalty when our king is on a pawnless flank + if (!(pos.pieces(PAWN) & KingFlank[file_of(ksq)])) + score -= PawnlessFlank; + + // Penalty if king flank is under attack, potentially moving toward the king + score -= FlankAttacks * kingFlankAttack; + + if (T) + Trace::add(KING, Us, score); + + return score; + } + + + // Evaluation::threats() assigns bonuses according to the types of the + // attacking and the attacked pieces. + + template template + Score Evaluation::threats() const { + + constexpr Color Them = ~Us; + constexpr Direction Up = pawn_push(Us); + constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + + Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe; + Score score = SCORE_ZERO; + + // Non-pawn enemies + nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN); + + // Squares strongly protected by the enemy, either because they defend the + // square with a pawn, or because they defend the square twice and we don't. + stronglyProtected = attackedBy[Them][PAWN] + | (attackedBy2[Them] & ~attackedBy2[Us]); + + // Non-pawn enemies, strongly protected + defended = nonPawnEnemies & stronglyProtected; + + // Enemies not strongly protected and under our attack + weak = pos.pieces(Them) & ~stronglyProtected & attackedBy[Us][ALL_PIECES]; + + // Bonus according to the kind of attacking pieces + if (defended | weak) + { + b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); + while (b) + score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(&b)))]; + + b = weak & attackedBy[Us][ROOK]; + while (b) + score += ThreatByRook[type_of(pos.piece_on(pop_lsb(&b)))]; + + if (weak & attackedBy[Us][KING]) + score += ThreatByKing; + + b = ~attackedBy[Them][ALL_PIECES] + | (nonPawnEnemies & attackedBy2[Us]); + score += Hanging * popcount(weak & b); + + // Additional bonus if weak piece is only protected by a queen + score += WeakQueenProtection * popcount(weak & attackedBy[Them][QUEEN]); + } + + // Bonus for restricting their piece moves + b = attackedBy[Them][ALL_PIECES] + & ~stronglyProtected + & attackedBy[Us][ALL_PIECES]; + score += RestrictedPiece * popcount(b); + + // Protected or unattacked squares + safe = ~attackedBy[Them][ALL_PIECES] | attackedBy[Us][ALL_PIECES]; + + // Bonus for attacking enemy pieces with our relatively safe pawns + b = pos.pieces(Us, PAWN) & safe; + b = pawn_attacks_bb(b) & nonPawnEnemies; + score += ThreatBySafePawn * popcount(b); + + // Find squares where our pawns can push on the next move + b = shift(pos.pieces(Us, PAWN)) & ~pos.pieces(); + b |= shift(b & TRank3BB) & ~pos.pieces(); + + // Keep only the squares which are relatively safe + b &= ~attackedBy[Them][PAWN] & safe; + + // Bonus for safe pawn threats on the next move + b = pawn_attacks_bb(b) & nonPawnEnemies; + score += ThreatByPawnPush * popcount(b); + + // Bonus for threats on the next moves against enemy queen + if (pos.count(Them) == 1) + { + bool queenImbalance = pos.count() == 1; + + Square s = pos.square(Them); + safe = mobilityArea[Us] + & ~pos.pieces(Us, PAWN) + & ~stronglyProtected; + + b = attackedBy[Us][KNIGHT] & attacks_bb(s); + + score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance); + + b = (attackedBy[Us][BISHOP] & attacks_bb(s, pos.pieces())) + | (attackedBy[Us][ROOK ] & attacks_bb(s, pos.pieces())); + + score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance); + } + + if (T) + Trace::add(THREAT, Us, score); + + return score; + } + + // Evaluation::passed() evaluates the passed pawns and candidate passed + // pawns of the given color. + + template template + Score Evaluation::passed() const { + + constexpr Color Them = ~Us; + constexpr Direction Up = pawn_push(Us); + constexpr Direction Down = -Up; + + auto king_proximity = [&](Color c, Square s) { + return std::min(distance(pos.square(c), s), 5); + }; + + Bitboard b, bb, squaresToQueen, unsafeSquares, blockedPassers, helpers; + Score score = SCORE_ZERO; + + b = pe->passed_pawns(Us); + + blockedPassers = b & shift(pos.pieces(Them, PAWN)); + if (blockedPassers) + { + helpers = shift(pos.pieces(Us, PAWN)) + & ~pos.pieces(Them) + & (~attackedBy2[Them] | attackedBy[Us][ALL_PIECES]); + + // Remove blocked candidate passers that don't have help to pass + b &= ~blockedPassers + | shift(helpers) + | shift(helpers); + } + + while (b) + { + Square s = pop_lsb(&b); + + assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); + + int r = relative_rank(Us, s); + + Score bonus = PassedRank[r]; + + if (r > RANK_3) + { + int w = 5 * r - 13; + Square blockSq = s + Up; + + // Adjust bonus based on the king's proximity + bonus += make_score(0, ( king_proximity(Them, blockSq) * 19 / 4 + - king_proximity(Us, blockSq) * 2) * w); + + // If blockSq is not the queening square then consider also a second push + if (r != RANK_7) + bonus -= make_score(0, king_proximity(Us, blockSq + Up) * w); + + // If the pawn is free to advance, then increase the bonus + if (pos.empty(blockSq)) + { + squaresToQueen = forward_file_bb(Us, s); + unsafeSquares = passed_pawn_span(Us, s); + + bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN); + + if (!(pos.pieces(Them) & bb)) + unsafeSquares &= attackedBy[Them][ALL_PIECES]; + + // If there are no enemy attacks on passed pawn span, assign a big bonus. + // Otherwise assign a smaller bonus if the path to queen is not attacked + // and even smaller bonus if it is attacked but block square is not. + int k = !unsafeSquares ? 35 : + !(unsafeSquares & squaresToQueen) ? 20 : + !(unsafeSquares & blockSq) ? 9 : + 0 ; + + // Assign a larger bonus if the block square is defended + if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq)) + k += 5; + + bonus += make_score(k * w, k * w); + } + } // r > RANK_3 + + score += bonus - PassedFile * edge_distance(file_of(s)); + } + + if (T) + Trace::add(PASSED, Us, score); + + return score; + } + + + // Evaluation::space() computes a space evaluation for a given side, aiming to improve game + // play in the opening. It is based on the number of safe squares on the four central files + // on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice. + // Finally, the space bonus is multiplied by a weight which decreases according to occupancy. + + template template + Score Evaluation::space() const { + + // Early exit if, for example, both queens or 6 minor pieces have been exchanged + if (pos.non_pawn_material() < SpaceThreshold) + return SCORE_ZERO; + + constexpr Color Them = ~Us; + constexpr Direction Down = -pawn_push(Us); + constexpr Bitboard SpaceMask = + Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB) + : CenterFiles & (Rank7BB | Rank6BB | Rank5BB); + + // Find the available squares for our pieces inside the area defined by SpaceMask + Bitboard safe = SpaceMask + & ~pos.pieces(Us, PAWN) + & ~attackedBy[Them][PAWN]; + + // Find all squares which are at most three squares behind some friendly pawn + Bitboard behind = pos.pieces(Us, PAWN); + behind |= shift(behind); + behind |= shift(behind); + + int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]); + int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); + Score score = make_score(bonus * weight * weight / 16, 0); + + if (T) + Trace::add(SPACE, Us, score); + + return score; + } + + + // Evaluation::winnable() adjusts the midgame and endgame score components, based on + // the known attacking/defending status of the players. The final value is derived + // by interpolation from the midgame and endgame values. + + template + Value Evaluation::winnable(Score score) const { + + int outflanking = distance(pos.square(WHITE), pos.square(BLACK)) + - distance(pos.square(WHITE), pos.square(BLACK)); + + bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) + && (pos.pieces(PAWN) & KingSide); + + bool almostUnwinnable = outflanking < 0 + && !pawnsOnBothFlanks; + + bool infiltration = rank_of(pos.square(WHITE)) > RANK_4 + || rank_of(pos.square(BLACK)) < RANK_5; + + // Compute the initiative bonus for the attacking side + int complexity = 9 * pe->passed_count() + + 12 * pos.count() + + 9 * outflanking + + 21 * pawnsOnBothFlanks + + 24 * infiltration + + 51 * !pos.non_pawn_material() + - 43 * almostUnwinnable + -110 ; + + Value mg = mg_value(score); + Value eg = eg_value(score); + + // Now apply the bonus: note that we find the attacking side by extracting the + // sign of the midgame or endgame values, and that we carefully cap the bonus + // so that the midgame and endgame scores do not change sign after the bonus. + int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0); + int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg)); + + mg += u; + eg += v; + + // Compute the scale factor for the winning side + Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; + int sf = me->scale_factor(pos, strongSide); + + // If scale factor is not already specific, scale down via general heuristics + if (sf == SCALE_FACTOR_NORMAL) + { + if (pos.opposite_bishops()) + { + if ( pos.non_pawn_material(WHITE) == BishopValueMg + && pos.non_pawn_material(BLACK) == BishopValueMg) + sf = 18 + 4 * popcount(pe->passed_pawns(strongSide)); + else + sf = 22 + 3 * pos.count(strongSide); + } + else if ( pos.non_pawn_material(WHITE) == RookValueMg + && pos.non_pawn_material(BLACK) == RookValueMg + && pos.count(strongSide) - pos.count(~strongSide) <= 1 + && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) + && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) + sf = 36; + else if (pos.count() == 1) + sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) + : pos.count(WHITE) + pos.count(WHITE)); + else + sf = std::min(sf, 36 + 7 * pos.count(strongSide)); + } + + // Interpolate between the middlegame and (scaled by 'sf') endgame score + v = mg * int(me->game_phase()) + + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL; + v /= PHASE_MIDGAME; + + if (T) + { + Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score))); + Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL)); + } + + return Value(v); + } + + + // Evaluation::value() is the main function of the class. It computes the various + // parts of the evaluation and returns the value of the position from the point + // of view of the side to move. + + template + Value Evaluation::value() { + + assert(!pos.checkers()); + + // Probe the material hash table + me = Material::probe(pos); + + // If we have a specialized evaluation function for the current material + // configuration, call it and return. + if (me->specialized_eval_exists()) + return me->evaluate(pos); + + // Initialize score by reading the incrementally updated scores included in + // the position object (material + piece square tables) and the material + // imbalance. Score is computed internally from the white point of view. + Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->contempt; + + // Probe the pawn hash table + pe = Pawns::probe(pos); + score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK); + + // Early exit if score is high + auto lazy_skip = [&](Value lazyThreshold) { + return abs(mg_value(score) + eg_value(score)) / 2 > lazyThreshold + pos.non_pawn_material() / 64; + }; + + if (lazy_skip(LazyThreshold1)) + goto make_v; + + // Main evaluation begins here + initialize(); + initialize(); + + // Pieces evaluated first (also populates attackedBy, attackedBy2). + // Note that the order of evaluation of the terms is left unspecified. + score += pieces() - pieces() + + pieces() - pieces() + + pieces() - pieces() + + pieces() - pieces(); + + score += mobility[WHITE] - mobility[BLACK]; + + // More complex interactions that require fully populated attack bitboards + score += king< WHITE>() - king< BLACK>() + + passed< WHITE>() - passed< BLACK>(); + + if (lazy_skip(LazyThreshold2)) + goto make_v; + + score += threats() - threats() + + space< WHITE>() - space< BLACK>(); + +make_v: + // Derive single value from mg and eg parts of score + Value v = winnable(score); + + // In case of tracing add all remaining individual evaluation terms + if (T) + { + Trace::add(MATERIAL, pos.psq_score()); + Trace::add(IMBALANCE, me->imbalance()); + Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK)); + Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); + } + + // Evaluation grain + v = (v / 16) * 16; + + // Side to move point of view + v = (pos.side_to_move() == WHITE ? v : -v) + Tempo; + + return v; + } + +} // namespace + + +/// evaluate() is the evaluator for the outer world. It returns a static +/// evaluation of the position from the point of view of the side to move. + +Value Eval::evaluate(const Position& pos) { + + Value v; + + evalHits++; + if (!Eval::useNNUE) + v = Evaluation(pos).value(); + else + { + // scale and shift NNUE for compatibility with search and classical evaluation + auto adjusted_NNUE = [&](){ nnueHits++; return NNUE::evaluate(pos) * 5 / 4 + Tempo; }; + + if (Eval::useNNUE == 2) { + v = adjusted_NNUE(); + } else { + // if there is PSQ imbalance use classical eval, with small probability if it is small + Value psq = Value(abs(eg_value(pos.psq_score()))); + int r50 = 16 + pos.rule50_count(); + bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50; + bool classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB)); + + v = classical ? Evaluation(pos).value() : adjusted_NNUE(); + + // if the classical eval is small and imbalance large, use NNUE nevertheless. + if ( largePsq + && abs(v) * 16 < NNUEThreshold2 * r50) + v = adjusted_NNUE(); + } + + } + + // Damp down the evaluation linearly when shuffling + v = v * (100 - pos.rule50_count()) / 100; + + // Guarantee evaluation does not hit the tablebase range + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + + return v; +} + +/// trace() is like evaluate(), but instead of returning a value, it returns +/// a string (suitable for outputting to stdout) that contains the detailed +/// descriptions and values of each evaluation term. Useful for debugging. +/// Trace scores are from white's point of view + +std::string Eval::trace(const Position& pos) { + + if (pos.checkers()) + return "Final evaluation: none (in check)"; + + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + + Value v; + + std::memset(scores, 0, sizeof(scores)); + + pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt + + v = Evaluation(pos).value(); + + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) + << " Term | White | Black | Total \n" + << " | MG EG | MG EG | MG EG \n" + << " ------------+-------------+-------------+------------\n" + << " Material | " << Term(MATERIAL) + << " Imbalance | " << Term(IMBALANCE) + << " Pawns | " << Term(PAWN) + << " Knights | " << Term(KNIGHT) + << " Bishops | " << Term(BISHOP) + << " Rooks | " << Term(ROOK) + << " Queens | " << Term(QUEEN) + << " Mobility | " << Term(MOBILITY) + << " King safety | " << Term(KING) + << " Threats | " << Term(THREAT) + << " Passed | " << Term(PASSED) + << " Space | " << Term(SPACE) + << " Winnable | " << Term(WINNABLE) + << " ------------+-------------+-------------+------------\n" + << " Total | " << Term(TOTAL); + + v = pos.side_to_move() == WHITE ? v : -v; + + ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n"; + + if (Eval::useNNUE) + { + v = NNUE::evaluate(pos); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "\nNNUE evaluation: " << to_cp(v) << " (white side)\n"; + } + + v = evaluate(pos); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n"; + + return ss.str(); +} diff --git a/engines/stockfish13/evaluate.h b/engines/stockfish13/evaluate.h new file mode 100644 index 0000000..8da0e30 --- /dev/null +++ b/engines/stockfish13/evaluate.h @@ -0,0 +1,56 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef EVALUATE_H_INCLUDED +#define EVALUATE_H_INCLUDED + +#include + +#include "types.h" + +class Position; + +namespace Eval { + + std::string trace(const Position& pos); + Value evaluate(const Position& pos); + + extern int useNNUE; + extern int64_t nnueHits; + extern int64_t evalHits; + extern std::string eval_file_loaded; + void init_NNUE(); + void verify_NNUE(); + + // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue + // for the build process (profile-build and fishtest) to work. Do not change the + // name of the macro, as it is used in the Makefile. + #define EvalFileDefaultName "nn-03744f8d56d8.nnue" + + namespace NNUE { + + Value evaluate(const Position& pos); + Value compute_eval(const Position& pos); + void update_eval(const Position& pos); + bool load_eval(std::string streamName, std::istream& stream); + + } // namespace NNUE + +} // namespace Eval + +#endif // #ifndef EVALUATE_H_INCLUDED diff --git a/engines/stockfish13/incbin/UNLICENCE b/engines/stockfish13/incbin/UNLICENCE new file mode 100644 index 0000000..32484ab --- /dev/null +++ b/engines/stockfish13/incbin/UNLICENCE @@ -0,0 +1,26 @@ +The file "incbin.h" is free and unencumbered software released into +the public domain by Dale Weiler, see: + + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/engines/stockfish13/incbin/incbin.h b/engines/stockfish13/incbin/incbin.h new file mode 100755 index 0000000..c19684d --- /dev/null +++ b/engines/stockfish13/incbin/incbin.h @@ -0,0 +1,368 @@ +/** + * @file incbin.h + * @author Dale Weiler + * @brief Utility for including binary files + * + * Facilities for including binary files into the current translation unit and + * making use from them externally in other translation units. + */ +#ifndef INCBIN_HDR +#define INCBIN_HDR +#include +#if defined(__AVX512BW__) || \ + defined(__AVX512CD__) || \ + defined(__AVX512DQ__) || \ + defined(__AVX512ER__) || \ + defined(__AVX512PF__) || \ + defined(__AVX512VL__) || \ + defined(__AVX512F__) +# define INCBIN_ALIGNMENT_INDEX 6 +#elif defined(__AVX__) || \ + defined(__AVX2__) +# define INCBIN_ALIGNMENT_INDEX 5 +#elif defined(__SSE__) || \ + defined(__SSE2__) || \ + defined(__SSE3__) || \ + defined(__SSSE3__) || \ + defined(__SSE4_1__) || \ + defined(__SSE4_2__) || \ + defined(__neon__) +# define INCBIN_ALIGNMENT_INDEX 4 +#elif ULONG_MAX != 0xffffffffu +# define INCBIN_ALIGNMENT_INDEX 3 +# else +# define INCBIN_ALIGNMENT_INDEX 2 +#endif + +/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */ +#define INCBIN_ALIGN_SHIFT_0 1 +#define INCBIN_ALIGN_SHIFT_1 2 +#define INCBIN_ALIGN_SHIFT_2 4 +#define INCBIN_ALIGN_SHIFT_3 8 +#define INCBIN_ALIGN_SHIFT_4 16 +#define INCBIN_ALIGN_SHIFT_5 32 +#define INCBIN_ALIGN_SHIFT_6 64 + +/* Actual alignment value */ +#define INCBIN_ALIGNMENT \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \ + INCBIN_ALIGNMENT_INDEX) + +/* Stringize */ +#define INCBIN_STR(X) \ + #X +#define INCBIN_STRINGIZE(X) \ + INCBIN_STR(X) +/* Concatenate */ +#define INCBIN_CAT(X, Y) \ + X ## Y +#define INCBIN_CONCATENATE(X, Y) \ + INCBIN_CAT(X, Y) +/* Deferred macro expansion */ +#define INCBIN_EVAL(X) \ + X +#define INCBIN_INVOKE(N, ...) \ + INCBIN_EVAL(N(__VA_ARGS__)) + +/* Green Hills uses a different directive for including binary data */ +#if defined(__ghs__) +# if (__ghs_asm == 2) +# define INCBIN_MACRO ".file" +/* Or consider the ".myrawdata" entry in the ld file */ +# else +# define INCBIN_MACRO "\tINCBIN" +# endif +#else +# define INCBIN_MACRO ".incbin" +#endif + +#ifndef _MSC_VER +# define INCBIN_ALIGN \ + __attribute__((aligned(INCBIN_ALIGNMENT))) +#else +# define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT)) +#endif + +#if defined(__arm__) || /* GNU C and RealView */ \ + defined(__arm) || /* Diab */ \ + defined(_ARM) /* ImageCraft */ +# define INCBIN_ARM +#endif + +#ifdef __GNUC__ +/* Utilize .balign where supported */ +# define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" +# define INCBIN_ALIGN_BYTE ".balign 1\n" +#elif defined(INCBIN_ARM) +/* + * On arm assemblers, the alignment value is calculated as (1 << n) where `n' is + * the shift count. This is the value passed to `.align' + */ +# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n" +# define INCBIN_ALIGN_BYTE ".align 0\n" +#else +/* We assume other inline assembler's treat `.align' as `.balign' */ +# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" +# define INCBIN_ALIGN_BYTE ".align 1\n" +#endif + +/* INCBIN_CONST is used by incbin.c generated files */ +#if defined(__cplusplus) +# define INCBIN_EXTERNAL extern "C" +# define INCBIN_CONST extern const +#else +# define INCBIN_EXTERNAL extern +# define INCBIN_CONST const +#endif + +/** + * @brief Optionally override the linker section into which data is emitted. + * + * @warning If you use this facility, you'll have to deal with platform-specific linker output + * section naming on your own + * + * Overriding the default linker output section, e.g for esp8266/Arduino: + * @code + * #define INCBIN_OUTPUT_SECTION ".irom.text" + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * // Data is emitted into program memory that never gets copied to RAM + * @endcode + */ +#if !defined(INCBIN_OUTPUT_SECTION) +# if defined(__APPLE__) +# define INCBIN_OUTPUT_SECTION ".const_data" +# else +# define INCBIN_OUTPUT_SECTION ".rodata" +# endif +#endif + +#if defined(__APPLE__) +/* The directives are different for Apple branded compilers */ +# define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n" +# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" +# define INCBIN_INT ".long " +# define INCBIN_MANGLE "_" +# define INCBIN_BYTE ".byte " +# define INCBIN_TYPE(...) +#else +# define INCBIN_SECTION ".section " INCBIN_OUTPUT_SECTION "\n" +# define INCBIN_GLOBAL(NAME) ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" +# if defined(__ghs__) +# define INCBIN_INT ".word " +# else +# define INCBIN_INT ".int " +# endif +# if defined(__USER_LABEL_PREFIX__) +# define INCBIN_MANGLE INCBIN_STRINGIZE(__USER_LABEL_PREFIX__) +# else +# define INCBIN_MANGLE "" +# endif +# if defined(INCBIN_ARM) +/* On arm assemblers, `@' is used as a line comment token */ +# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n" +# elif defined(__MINGW32__) || defined(__MINGW64__) +/* Mingw doesn't support this directive either */ +# define INCBIN_TYPE(NAME) +# else +/* It's safe to use `@' on other architectures */ +# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n" +# endif +# define INCBIN_BYTE ".byte " +#endif + +/* List of style types used for symbol names */ +#define INCBIN_STYLE_CAMEL 0 +#define INCBIN_STYLE_SNAKE 1 + +/** + * @brief Specify the prefix to use for symbol names. + * + * By default this is `g', producing symbols of the form: + * @code + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * + * // Now you have the following symbols: + * // const unsigned char gFooData[]; + * // const unsigned char *const gFooEnd; + * // const unsigned int gFooSize; + * @endcode + * + * If however you specify a prefix before including: e.g: + * @code + * #define INCBIN_PREFIX incbin + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * + * // Now you have the following symbols instead: + * // const unsigned char incbinFooData[]; + * // const unsigned char *const incbinFooEnd; + * // const unsigned int incbinFooSize; + * @endcode + */ +#if !defined(INCBIN_PREFIX) +# define INCBIN_PREFIX g +#endif + +/** + * @brief Specify the style used for symbol names. + * + * Possible options are + * - INCBIN_STYLE_CAMEL "CamelCase" + * - INCBIN_STYLE_SNAKE "snake_case" + * + * Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form: + * @code + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * + * // Now you have the following symbols: + * // const unsigned char FooData[]; + * // const unsigned char *const FooEnd; + * // const unsigned int FooSize; + * @endcode + * + * If however you specify a style before including: e.g: + * @code + * #define INCBIN_STYLE INCBIN_STYLE_SNAKE + * #include "incbin.h" + * INCBIN(foo, "foo.txt"); + * + * // Now you have the following symbols: + * // const unsigned char foo_data[]; + * // const unsigned char *const foo_end; + * // const unsigned int foo_size; + * @endcode + */ +#if !defined(INCBIN_STYLE) +# define INCBIN_STYLE INCBIN_STYLE_CAMEL +#endif + +/* Style lookup tables */ +#define INCBIN_STYLE_0_DATA Data +#define INCBIN_STYLE_0_END End +#define INCBIN_STYLE_0_SIZE Size +#define INCBIN_STYLE_1_DATA _data +#define INCBIN_STYLE_1_END _end +#define INCBIN_STYLE_1_SIZE _size + +/* Style lookup: returning identifier */ +#define INCBIN_STYLE_IDENT(TYPE) \ + INCBIN_CONCATENATE( \ + INCBIN_STYLE_, \ + INCBIN_CONCATENATE( \ + INCBIN_EVAL(INCBIN_STYLE), \ + INCBIN_CONCATENATE(_, TYPE))) + +/* Style lookup: returning string literal */ +#define INCBIN_STYLE_STRING(TYPE) \ + INCBIN_STRINGIZE( \ + INCBIN_STYLE_IDENT(TYPE)) \ + +/* Generate the global labels by indirectly invoking the macro with our style + * type and concatenating the name against them. */ +#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \ + INCBIN_INVOKE( \ + INCBIN_GLOBAL, \ + INCBIN_CONCATENATE( \ + NAME, \ + INCBIN_INVOKE( \ + INCBIN_STYLE_IDENT, \ + TYPE))) \ + INCBIN_INVOKE( \ + INCBIN_TYPE, \ + INCBIN_CONCATENATE( \ + NAME, \ + INCBIN_INVOKE( \ + INCBIN_STYLE_IDENT, \ + TYPE))) + +/** + * @brief Externally reference binary data included in another translation unit. + * + * Produces three external symbols that reference the binary data included in + * another translation unit. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name given for the binary data + * + * @code + * INCBIN_EXTERN(Foo); + * + * // Now you have the following symbols: + * // extern const unsigned char FooData[]; + * // extern const unsigned char *const FooEnd; + * // extern const unsigned int FooSize; + * @endcode + */ +#define INCBIN_EXTERN(NAME) \ + INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(DATA))[]; \ + INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char *const \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(END)); \ + INCBIN_EXTERNAL const unsigned int \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(SIZE)) + +/** + * @brief Include a binary file into the current translation unit. + * + * Includes a binary file into the current translation unit, producing three symbols + * for objects that encode the data and size respectively. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name to associate with this binary data (as an identifier.) + * @param FILENAME The file to include (as a string literal.) + * + * @code + * INCBIN(Icon, "icon.png"); + * + * // Now you have the following symbols: + * // const unsigned char IconData[]; + * // const unsigned char *const IconEnd; + * // const unsigned int IconSize; + * @endcode + * + * @warning This must be used in global scope + * @warning The identifiers may be different if INCBIN_STYLE is not default + * + * To externally reference the data included by this in another translation unit + * please @see INCBIN_EXTERN. + */ +#ifdef _MSC_VER +#define INCBIN(NAME, FILENAME) \ + INCBIN_EXTERN(NAME) +#else +#define INCBIN(NAME, FILENAME) \ + __asm__(INCBIN_SECTION \ + INCBIN_GLOBAL_LABELS(NAME, DATA) \ + INCBIN_ALIGN_HOST \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \ + INCBIN_MACRO " \"" FILENAME "\"\n" \ + INCBIN_GLOBAL_LABELS(NAME, END) \ + INCBIN_ALIGN_BYTE \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \ + INCBIN_BYTE "1\n" \ + INCBIN_GLOBAL_LABELS(NAME, SIZE) \ + INCBIN_ALIGN_HOST \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \ + INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \ + INCBIN_ALIGN_HOST \ + ".text\n" \ + ); \ + INCBIN_EXTERN(NAME) + +#endif +#endif diff --git a/engines/stockfish13/main.cpp b/engines/stockfish13/main.cpp new file mode 100644 index 0000000..39d22af --- /dev/null +++ b/engines/stockfish13/main.cpp @@ -0,0 +1,53 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitboard.h" +#include "endgame.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "syzygy/tbprobe.h" + +namespace PSQT { + void init(); +} + +void _stockfish_initialize() { +//int main(int argc, char* argv[]) { +// std::cout << engine_info() << std::endl; +// CommandLine::init(argc, argv); + + UCI::init(Options); + Tune::init(); + PSQT::init(); + Bitboards::init(); + Position::init(); + Bitbases::init(); + Endgames::init(); + Threads.set(size_t(Options["Threads"])); + Search::clear(); // After threads are up + Eval::init_NNUE(); + +// UCI::loop(argc, argv); +// Threads.set(0); +// return 0; +} diff --git a/engines/stockfish13/material.cpp b/engines/stockfish13/material.cpp new file mode 100644 index 0000000..870a5e1 --- /dev/null +++ b/engines/stockfish13/material.cpp @@ -0,0 +1,220 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include // For std::memset + +#include "material.h" +#include "thread.h" + +using namespace std; + +namespace { + + // Polynomial material imbalance parameters + + constexpr int QuadraticOurs[][PIECE_TYPE_NB] = { + // OUR PIECES + // pair pawn knight bishop rook queen + {1438 }, // Bishop pair + { 40, 38 }, // Pawn + { 32, 255, -62 }, // Knight OUR PIECES + { 0, 104, 4, 0 }, // Bishop + { -26, -2, 47, 105, -208 }, // Rook + {-189, 24, 117, 133, -134, -6 } // Queen + }; + + constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = { + // THEIR PIECES + // pair pawn knight bishop rook queen + { }, // Bishop pair + { 36, }, // Pawn + { 9, 63, }, // Knight OUR PIECES + { 59, 65, 42, }, // Bishop + { 46, 39, 24, -24, }, // Rook + { 97, 100, -42, 137, 268, } // Queen + }; + + // Endgame evaluation and scaling functions are accessed directly and not through + // the function maps because they correspond to more than one material hash key. + Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; + + Endgame ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKQKRPs[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKPsK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKPKP[] = { Endgame(WHITE), Endgame(BLACK) }; + + // Helper used to detect a given material distribution + bool is_KXK(const Position& pos, Color us) { + return !more_than_one(pos.pieces(~us)) + && pos.non_pawn_material(us) >= RookValueMg; + } + + bool is_KBPsK(const Position& pos, Color us) { + return pos.non_pawn_material(us) == BishopValueMg + && pos.count(us) >= 1; + } + + bool is_KQKRPs(const Position& pos, Color us) { + return !pos.count(us) + && pos.non_pawn_material(us) == QueenValueMg + && pos.count(~us) == 1 + && pos.count(~us) >= 1; + } + + + /// imbalance() calculates the imbalance by comparing the piece count of each + /// piece type for both colors. + + template + int imbalance(const int pieceCount[][PIECE_TYPE_NB]) { + + constexpr Color Them = ~Us; + + int bonus = 0; + + // Second-degree polynomial material imbalance, by Tord Romstad + for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) + { + if (!pieceCount[Us][pt1]) + continue; + + int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1]; + + for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2) + v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2] + + QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2]; + + bonus += pieceCount[Us][pt1] * v; + } + + return bonus; + } + +} // namespace + +namespace Material { + + +/// Material::probe() looks up the current position's material configuration in +/// the material hash table. It returns a pointer to the Entry if the position +/// is found. Otherwise a new Entry is computed and stored there, so we don't +/// have to recompute all when the same material configuration occurs again. + +Entry* probe(const Position& pos) { + + Key key = pos.material_key(); + Entry* e = pos.this_thread()->materialTable[key]; + + if (e->key == key) + return e; + + std::memset(e, 0, sizeof(Entry)); + e->key = key; + e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; + + Value npm_w = pos.non_pawn_material(WHITE); + Value npm_b = pos.non_pawn_material(BLACK); + Value npm = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit); + + // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME] + e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); + + // Let's look if we have a specialized evaluation function for this particular + // material configuration. Firstly we look for a fixed configuration one, then + // for a generic one if the previous search failed. + if ((e->evaluationFunction = Endgames::probe(key)) != nullptr) + return e; + + for (Color c : { WHITE, BLACK }) + if (is_KXK(pos, c)) + { + e->evaluationFunction = &EvaluateKXK[c]; + return e; + } + + // OK, we didn't find any special evaluation function for the current material + // configuration. Is there a suitable specialized scaling function? + const auto* sf = Endgames::probe(key); + + if (sf) + { + e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned + return e; + } + + // We didn't find any specialized scaling function, so fall back on generic + // ones that refer to more than one material distribution. Note that in this + // case we don't return after setting the function. + for (Color c : { WHITE, BLACK }) + { + if (is_KBPsK(pos, c)) + e->scalingFunction[c] = &ScaleKBPsK[c]; + + else if (is_KQKRPs(pos, c)) + e->scalingFunction[c] = &ScaleKQKRPs[c]; + } + + if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board + { + if (!pos.count(BLACK)) + { + assert(pos.count(WHITE) >= 2); + + e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; + } + else if (!pos.count(WHITE)) + { + assert(pos.count(BLACK) >= 2); + + e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; + } + else if (pos.count(WHITE) == 1 && pos.count(BLACK) == 1) + { + // This is a special case because we set scaling functions + // for both colors instead of only one. + e->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; + e->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; + } + } + + // Zero or just one pawn makes it difficult to win, even with a small material + // advantage. This catches some trivial draws like KK, KBK and KNK and gives a + // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN). + if (!pos.count(WHITE) && npm_w - npm_b <= BishopValueMg) + e->factor[WHITE] = uint8_t(npm_w < RookValueMg ? SCALE_FACTOR_DRAW : + npm_b <= BishopValueMg ? 4 : 14); + + if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg) + e->factor[BLACK] = uint8_t(npm_b < RookValueMg ? SCALE_FACTOR_DRAW : + npm_w <= BishopValueMg ? 4 : 14); + + // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder + // for the bishop pair "extended piece", which allows us to be more flexible + // in defining bishop pair bonuses. + const int pieceCount[COLOR_NB][PIECE_TYPE_NB] = { + { pos.count(WHITE) > 1, pos.count(WHITE), pos.count(WHITE), + pos.count(WHITE) , pos.count(WHITE), pos.count(WHITE) }, + { pos.count(BLACK) > 1, pos.count(BLACK), pos.count(BLACK), + pos.count(BLACK) , pos.count(BLACK), pos.count(BLACK) } }; + + e->value = int16_t((imbalance(pieceCount) - imbalance(pieceCount)) / 16); + return e; +} + +} // namespace Material diff --git a/engines/stockfish13/material.h b/engines/stockfish13/material.h new file mode 100644 index 0000000..80d0165 --- /dev/null +++ b/engines/stockfish13/material.h @@ -0,0 +1,71 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MATERIAL_H_INCLUDED +#define MATERIAL_H_INCLUDED + +#include "endgame.h" +#include "misc.h" +#include "position.h" +#include "types.h" + +namespace Material { + +/// Material::Entry contains various information about a material configuration. +/// It contains a material imbalance evaluation, a function pointer to a special +/// endgame evaluation function (which in most cases is NULL, meaning that the +/// standard evaluation function will be used), and scale factors. +/// +/// The scale factors are used to scale the evaluation score up or down. For +/// instance, in KRB vs KR endgames, the score is scaled down by a factor of 4, +/// which will result in scores of absolute value less than one pawn. + +struct Entry { + + Score imbalance() const { return make_score(value, value); } + Phase game_phase() const { return gamePhase; } + bool specialized_eval_exists() const { return evaluationFunction != nullptr; } + Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } + + // scale_factor() takes a position and a color as input and returns a scale factor + // for the given color. We have to provide the position in addition to the color + // because the scale factor may also be a function which should be applied to + // the position. For instance, in KBP vs K endgames, the scaling function looks + // for rook pawns and wrong-colored bishops. + ScaleFactor scale_factor(const Position& pos, Color c) const { + ScaleFactor sf = scalingFunction[c] ? (*scalingFunction[c])(pos) + : SCALE_FACTOR_NONE; + return sf != SCALE_FACTOR_NONE ? sf : ScaleFactor(factor[c]); + } + + Key key; + const EndgameBase* evaluationFunction; + const EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each + // side (e.g. KPKP, KBPsK) + int16_t value; + uint8_t factor[COLOR_NB]; + Phase gamePhase; +}; + +typedef HashTable Table; + +Entry* probe(const Position& pos); + +} // namespace Material + +#endif // #ifndef MATERIAL_H_INCLUDED diff --git a/engines/stockfish13/misc.cpp b/engines/stockfish13/misc.cpp new file mode 100644 index 0000000..d9bc47e --- /dev/null +++ b/engines/stockfish13/misc.cpp @@ -0,0 +1,643 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef _WIN32 +#if _WIN32_WINNT < 0x0601 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +// The needed Windows API for processor groups could be missed from old Windows +// versions, so instead of calling them directly (forcing the linker to resolve +// the calls at compile time), try to load them at runtime. To do this we need +// first to define the corresponding function pointers. +extern "C" { +typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); +typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY); +typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); +} +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) && !defined(__ANDROID__) +#include +#include +#endif + +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) +#define POSIXALIGNEDALLOC +#include +#endif + +#include "misc.h" +#include "thread.h" + +using namespace std; + +namespace { + +/// Version number. If Version is left empty, then compile date in the format +/// DD-MM-YY and show in engine_info. +const string Version = ""; + +/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and +/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We +/// can toggle the logging of std::cout and std:cin at runtime whilst preserving +/// usual I/O functionality, all without changing a single line of code! +/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 + +struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout + + Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {} + + int sync() override { return logBuf->pubsync(), buf->pubsync(); } + int overflow(int c) override { return log(buf->sputc((char)c), "<< "); } + int underflow() override { return buf->sgetc(); } + int uflow() override { return log(buf->sbumpc(), ">> "); } + + streambuf *buf, *logBuf; + + int log(int c, const char* prefix) { + + static int last = '\n'; // Single log file + + if (last == '\n') + logBuf->sputn(prefix, 3); + + return last = logBuf->sputc((char)c); + } +}; + +class Logger { + + Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {} + ~Logger() { start(""); } + + ofstream file; + Tie in, out; + +public: + static void start(const std::string& fname) { + + static Logger l; + + if (!fname.empty() && !l.file.is_open()) + { + l.file.open(fname, ifstream::out); + + if (!l.file.is_open()) + { + cerr << "Unable to open debug log file " << fname << endl; + exit(EXIT_FAILURE); + } + + cin.rdbuf(&l.in); + cout.rdbuf(&l.out); + } + else if (fname.empty() && l.file.is_open()) + { + cout.rdbuf(l.out.buf); + cin.rdbuf(l.in.buf); + l.file.close(); + } + } +}; + +} // namespace + + +/// engine_info() returns the full name of the current Stockfish version. This +/// will be either "Stockfish DD-MM-YY" (where DD-MM-YY is the date when +/// the program was compiled) or "Stockfish ", depending on whether +/// Version is empty. + +const string engine_info(bool to_uci) { + + const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + string month, day, year; + stringstream ss, date(__DATE__); // From compiler, format is "Sep 21 2008" + + ss << "Stockfish " << Version << setfill('0'); + + if (Version.empty()) + { + date >> month >> day >> year; + ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2); + } + + ss << (to_uci ? "\nid author ": " by ") + << "the Stockfish developers (see AUTHORS file)"; + + return ss.str(); +} + + +/// compiler_info() returns a string trying to describe the compiler we use + +const std::string compiler_info() { + + #define stringify2(x) #x + #define stringify(x) stringify2(x) + #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch) + +/// Predefined macros hell: +/// +/// __GNUC__ Compiler is gcc, Clang or Intel on Linux +/// __INTEL_COMPILER Compiler is Intel +/// _MSC_VER Compiler is MSVC or Intel on Windows +/// _WIN32 Building on Windows (any) +/// _WIN64 Building on Windows 64 bit + + std::string compiler = "\nCompiled by "; + + #ifdef __clang__ + compiler += "clang++ "; + compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); + #elif __INTEL_COMPILER + compiler += "Intel compiler "; + compiler += "(version "; + compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE); + compiler += ")"; + #elif _MSC_VER + compiler += "MSVC "; + compiler += "(version "; + compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); + compiler += ")"; + #elif __GNUC__ + compiler += "g++ (GNUC) "; + compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); + #else + compiler += "Unknown compiler "; + compiler += "(unknown version)"; + #endif + + #if defined(__APPLE__) + compiler += " on Apple"; + #elif defined(__CYGWIN__) + compiler += " on Cygwin"; + #elif defined(__MINGW64__) + compiler += " on MinGW64"; + #elif defined(__MINGW32__) + compiler += " on MinGW32"; + #elif defined(__ANDROID__) + compiler += " on Android"; + #elif defined(__linux__) + compiler += " on Linux"; + #elif defined(_WIN64) + compiler += " on Microsoft Windows 64-bit"; + #elif defined(_WIN32) + compiler += " on Microsoft Windows 32-bit"; + #else + compiler += " on unknown system"; + #endif + + compiler += "\nCompilation settings include: "; + compiler += (Is64Bit ? " 64bit" : " 32bit"); + #if defined(USE_VNNI) + compiler += " VNNI"; + #endif + #if defined(USE_AVX512) + compiler += " AVX512"; + #endif + compiler += (HasPext ? " BMI2" : ""); + #if defined(USE_AVX2) + compiler += " AVX2"; + #endif + #if defined(USE_SSE41) + compiler += " SSE41"; + #endif + #if defined(USE_SSSE3) + compiler += " SSSE3"; + #endif + #if defined(USE_SSE2) + compiler += " SSE2"; + #endif + compiler += (HasPopCnt ? " POPCNT" : ""); + #if defined(USE_MMX) + compiler += " MMX"; + #endif + #if defined(USE_NEON) + compiler += " NEON"; + #endif + + #if !defined(NDEBUG) + compiler += " DEBUG"; + #endif + + compiler += "\n__VERSION__ macro expands to: "; + #ifdef __VERSION__ + compiler += __VERSION__; + #else + compiler += "(undefined macro)"; + #endif + compiler += "\n"; + + return compiler; +} + + +/// Debug functions used mainly to collect run-time statistics +static std::atomic hits[2], means[2]; + +void dbg_hit_on(bool b) { ++hits[0]; if (b) ++hits[1]; } +void dbg_hit_on(bool c, bool b) { if (c) dbg_hit_on(b); } +void dbg_mean_of(int v) { ++means[0]; means[1] += v; } + +void dbg_print() { + + if (hits[0]) + cerr << "Total " << hits[0] << " Hits " << hits[1] + << " hit rate (%) " << 100 * hits[1] / hits[0] << endl; + + if (means[0]) + cerr << "Total " << means[0] << " Mean " + << (double)means[1] / means[0] << endl; +} + + +/// Used to serialize access to std::cout to avoid multiple threads writing at +/// the same time. + +std::ostream& operator<<(std::ostream& os, SyncCout sc) { + + static std::mutex m; + + if (sc == IO_LOCK) + m.lock(); + + if (sc == IO_UNLOCK) + m.unlock(); + + return os; +} + + +/// Trampoline helper to avoid moving Logger to misc.h +void start_logger(const std::string& fname) { Logger::start(fname); } + + +/// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking +/// function that doesn't stall the CPU waiting for data to be loaded from memory, +/// which can be quite slow. +#ifdef NO_PREFETCH + +void prefetch(void*) {} + +#else + +void prefetch(void* addr) { + +# if defined(__INTEL_COMPILER) + // This hack prevents prefetches from being optimized away by + // Intel compiler. Both MSVC and gcc seem not be affected by this. + __asm__ (""); +# endif + +# if defined(__INTEL_COMPILER) || defined(_MSC_VER) + _mm_prefetch((char*)addr, _MM_HINT_T0); +# else + __builtin_prefetch(addr); +# endif +} + +#endif + + +/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation +/// does not guarantee the availability of aligned_alloc(). Memory allocated with +/// std_aligned_alloc() must be freed with std_aligned_free(). + +void* std_aligned_alloc(size_t alignment, size_t size) { + +#if defined(POSIXALIGNEDALLOC) + void *mem; + return posix_memalign(&mem, alignment, size) ? nullptr : mem; +#elif defined(_WIN32) + return _mm_malloc(size, alignment); +#else + return std::aligned_alloc(alignment, size); +#endif +} + +void std_aligned_free(void* ptr) { + +#if defined(POSIXALIGNEDALLOC) + free(ptr); +#elif defined(_WIN32) + _mm_free(ptr); +#else + free(ptr); +#endif +} + +/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. + +#if defined(_WIN32) + +static void* aligned_large_pages_alloc_win(size_t allocSize) { + + HANDLE hProcessToken { }; + LUID luid { }; + void* mem = nullptr; + + const size_t largePageSize = GetLargePageMinimum(); + if (!largePageSize) + return nullptr; + + // We need SeLockMemoryPrivilege, so try to enable it for the process + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; + + if (LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid)) + { + TOKEN_PRIVILEGES tp { }; + TOKEN_PRIVILEGES prevTp { }; + DWORD prevTpLen = 0; + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, + // we still need to query GetLastError() to ensure that the privileges were actually obtained. + if (AdjustTokenPrivileges( + hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && + GetLastError() == ERROR_SUCCESS) + { + // Round up size to full pages and allocate + allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); + mem = VirtualAlloc( + NULL, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); + + // Privilege no longer needed, restore previous state + AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, NULL, NULL); + } + } + + CloseHandle(hProcessToken); + + return mem; +} + +void* aligned_large_pages_alloc(size_t allocSize) { + + static bool firstCall = true; + void* mem; + + // Try to allocate large pages + mem = aligned_large_pages_alloc_win(allocSize); + + // Suppress info strings on the first call. The first call occurs before 'uci' + // is received and in that case this output confuses some GUIs. + if (!firstCall) + { + if (mem) + sync_cout << "info string Hash table allocation: Windows large pages used." << sync_endl; + else + sync_cout << "info string Hash table allocation: Windows large pages not used." << sync_endl; + } + firstCall = false; + + // Fall back to regular, page aligned, allocation if necessary + if (!mem) + mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + + return mem; +} + +#else + +void* aligned_large_pages_alloc(size_t allocSize) { + +#if defined(__linux__) + constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size +#else + constexpr size_t alignment = 4096; // assumed small page size +#endif + + // round up to multiples of alignment + size_t size = ((allocSize + alignment - 1) / alignment) * alignment; + void *mem = std_aligned_alloc(alignment, size); +#if defined(MADV_HUGEPAGE) + madvise(mem, size, MADV_HUGEPAGE); +#endif + return mem; +} + +#endif + + +/// aligned_large_pages_free() will free the previously allocated ttmem + +#if defined(_WIN32) + +void aligned_large_pages_free(void* mem) { + + if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) + { + DWORD err = GetLastError(); + std::cerr << "Failed to free transposition table. Error code: 0x" << + std::hex << err << std::dec << std::endl; + exit(EXIT_FAILURE); + } +} + +#else + +void aligned_large_pages_free(void *mem) { + std_aligned_free(mem); +} + +#endif + + +namespace WinProcGroup { + +#ifndef _WIN32 + +void bindThisThread(size_t) {} + +#else + +/// best_group() retrieves logical processor information using Windows specific +/// API and returns the best group id for the thread with index idx. Original +/// code from Texel by Peter Österlund. + +int best_group(size_t idx) { + + int threads = 0; + int nodes = 0; + int cores = 0; + DWORD returnLength = 0; + DWORD byteOffset = 0; + + // Early exit if the needed API is not available at runtime + HMODULE k32 = GetModuleHandle("Kernel32.dll"); + auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx"); + if (!fun1) + return -1; + + // First call to get returnLength. We expect it to fail due to null buffer + if (fun1(RelationAll, nullptr, &returnLength)) + return -1; + + // Once we know returnLength, allocate the buffer + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; + ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength); + + // Second call, now we expect to succeed + if (!fun1(RelationAll, buffer, &returnLength)) + { + free(buffer); + return -1; + } + + while (byteOffset < returnLength) + { + if (ptr->Relationship == RelationNumaNode) + nodes++; + + else if (ptr->Relationship == RelationProcessorCore) + { + cores++; + threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; + } + + assert(ptr->Size); + byteOffset += ptr->Size; + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); + } + + free(buffer); + + std::vector groups; + + // Run as many threads as possible on the same node until core limit is + // reached, then move on filling the next node. + for (int n = 0; n < nodes; n++) + for (int i = 0; i < cores / nodes; i++) + groups.push_back(n); + + // In case a core has more than one logical processor (we assume 2) and we + // have still threads to allocate, then spread them evenly across available + // nodes. + for (int t = 0; t < threads - cores; t++) + groups.push_back(t % nodes); + + // If we still have more threads than the total number of logical processors + // then return -1 and let the OS to decide what to do. + return idx < groups.size() ? groups[idx] : -1; +} + + +/// bindThisThread() set the group affinity of the current thread + +void bindThisThread(size_t idx) { + + // Use only local variables to be thread-safe + int group = best_group(idx); + + if (group == -1) + return; + + // Early exit if the needed API are not available at runtime + HMODULE k32 = GetModuleHandle("Kernel32.dll"); + auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); + auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity"); + + if (!fun2 || !fun3) + return; + + GROUP_AFFINITY affinity; + if (fun2(group, &affinity)) + fun3(GetCurrentThread(), &affinity, nullptr); +} + +#endif + +} // namespace WinProcGroup + +#ifdef _WIN32 +#include +#define GETCWD _getcwd +#else +#include +#define GETCWD getcwd +#endif + +namespace CommandLine { + +string argv0; // path+name of the executable binary, as given by argv[0] +string binaryDirectory; // path of the executable directory +string workingDirectory; // path of the working directory +string pathSeparator; // Separator for our current OS + +void init(int argc, char* argv[]) { + (void)argc; + string separator; + + // extract the path+name of the executable binary + argv0 = argv[0]; + +#ifdef _WIN32 + pathSeparator = "\\"; + #ifdef _MSC_VER + // Under windows argv[0] may not have the extension. Also _get_pgmptr() had + // issues in some windows 10 versions, so check returned values carefully. + char* pgmptr = nullptr; + if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) + argv0 = pgmptr; + #endif +#else + pathSeparator = "/"; +#endif + + // extract the working directory + workingDirectory = ""; + char buff[40000]; + char* cwd = GETCWD(buff, 40000); + if (cwd) + workingDirectory = cwd; + + // extract the binary directory path from argv0 + binaryDirectory = argv0; + size_t pos = binaryDirectory.find_last_of("\\/"); + if (pos == std::string::npos) + binaryDirectory = "." + pathSeparator; + else + binaryDirectory.resize(pos + 1); + + // pattern replacement: "./" at the start of path is replaced by the working directory + if (binaryDirectory.find("." + pathSeparator) == 0) + binaryDirectory.replace(0, 1, workingDirectory); +} + + +} // namespace CommandLine diff --git a/engines/stockfish13/misc.h b/engines/stockfish13/misc.h new file mode 100644 index 0000000..bc48f30 --- /dev/null +++ b/engines/stockfish13/misc.h @@ -0,0 +1,134 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MISC_H_INCLUDED +#define MISC_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "types.h" + +const std::string engine_info(bool to_uci = false); +const std::string compiler_info(); +void prefetch(void* addr); +void start_logger(const std::string& fname); +void* std_aligned_alloc(size_t alignment, size_t size); +void std_aligned_free(void* ptr); +void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes +void aligned_large_pages_free(void* mem); // nop if mem == nullptr + +void dbg_hit_on(bool b); +void dbg_hit_on(bool c, bool b); +void dbg_mean_of(int v); +void dbg_print(); + +typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds +static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); +inline TimePoint now() { + return std::chrono::duration_cast + (std::chrono::steady_clock::now().time_since_epoch()).count(); +} + +template +struct HashTable { + Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; } + +private: + std::vector table = std::vector(Size); // Allocate on the heap +}; + + +enum SyncCout { IO_LOCK, IO_UNLOCK }; +std::ostream& operator<<(std::ostream&, SyncCout); + +#define sync_cout std::cout << IO_LOCK +#define sync_endl std::endl << IO_UNLOCK + + +/// xorshift64star Pseudo-Random Number Generator +/// This class is based on original code written and dedicated +/// to the public domain by Sebastiano Vigna (2014). +/// It has the following characteristics: +/// +/// - Outputs 64-bit numbers +/// - Passes Dieharder and SmallCrush test batteries +/// - Does not require warm-up, no zeroland to escape +/// - Internal state is a single 64-bit integer +/// - Period is 2^64 - 1 +/// - Speed: 1.60 ns/call (Core i7 @3.40GHz) +/// +/// For further analysis see +/// + +class PRNG { + + uint64_t s; + + uint64_t rand64() { + + s ^= s >> 12, s ^= s << 25, s ^= s >> 27; + return s * 2685821657736338717LL; + } + +public: + PRNG(uint64_t seed) : s(seed) { assert(seed); } + + template T rand() { return T(rand64()); } + + /// Special generator used to fast init magic numbers. + /// Output values only have 1/8th of their bits set on average. + template T sparse_rand() + { return T(rand64() & rand64() & rand64()); } +}; + +inline uint64_t mul_hi64(uint64_t a, uint64_t b) { +#if defined(__GNUC__) && defined(IS_64BIT) + __extension__ typedef unsigned __int128 uint128; + return ((uint128)a * (uint128)b) >> 64; +#else + uint64_t aL = (uint32_t)a, aH = a >> 32; + uint64_t bL = (uint32_t)b, bH = b >> 32; + uint64_t c1 = (aL * bL) >> 32; + uint64_t c2 = aH * bL + c1; + uint64_t c3 = aL * bH + (uint32_t)c2; + return aH * bH + (c2 >> 32) + (c3 >> 32); +#endif +} + +/// Under Windows it is not possible for a process to run on more than one +/// logical processor group. This usually means to be limited to use max 64 +/// cores. To overcome this, some special platform specific API should be +/// called to set group affinity for each thread. Original code from Texel by +/// Peter Österlund. + +namespace WinProcGroup { + void bindThisThread(size_t idx); +} + +namespace CommandLine { + void init(int argc, char* argv[]); + + extern std::string binaryDirectory; // path of the executable directory + extern std::string workingDirectory; // path of the working directory +} + +#endif // #ifndef MISC_H_INCLUDED diff --git a/engines/stockfish13/movegen.cpp b/engines/stockfish13/movegen.cpp new file mode 100644 index 0000000..3340f65 --- /dev/null +++ b/engines/stockfish13/movegen.cpp @@ -0,0 +1,367 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "movegen.h" +#include "position.h" + +namespace { + + template + ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) { + + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + { + *moveList++ = make(to - D, to, QUEEN); + if (attacks_bb(to) & ksq) + *moveList++ = make(to - D, to, KNIGHT); + } + + if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) + { + *moveList++ = make(to - D, to, ROOK); + *moveList++ = make(to - D, to, BISHOP); + if (!(attacks_bb(to) & ksq)) + *moveList++ = make(to - D, to, KNIGHT); + } + + return moveList; + } + + + template + ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { + + constexpr Color Them = ~Us; + constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + constexpr Direction Up = pawn_push(Us); + constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + + const Square ksq = pos.square(Them); + Bitboard emptySquares; + + Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; + Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; + + Bitboard enemies = (Type == EVASIONS ? pos.pieces(Them) & target: + Type == CAPTURES ? target : pos.pieces(Them)); + + // Single and double pawn pushes, no promotions + if (Type != CAPTURES) + { + emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces()); + + Bitboard b1 = shift(pawnsNotOn7) & emptySquares; + Bitboard b2 = shift(b1 & TRank3BB) & emptySquares; + + if (Type == EVASIONS) // Consider only blocking squares + { + b1 &= target; + b2 &= target; + } + + if (Type == QUIET_CHECKS) + { + b1 &= pawn_attacks_bb(Them, ksq); + b2 &= pawn_attacks_bb(Them, ksq); + + // Add pawn pushes which give discovered check. This is possible only + // if the pawn is not on the same file as the enemy king, because we + // don't generate captures. Note that a possible discovery check + // promotion has been already generated amongst the captures. + Bitboard dcCandidateQuiets = pos.blockers_for_king(Them) & pawnsNotOn7; + if (dcCandidateQuiets) + { + Bitboard dc1 = shift(dcCandidateQuiets) & emptySquares & ~file_bb(ksq); + Bitboard dc2 = shift(dc1 & TRank3BB) & emptySquares; + + b1 |= dc1; + b2 |= dc2; + } + } + + while (b1) + { + Square to = pop_lsb(&b1); + *moveList++ = make_move(to - Up, to); + } + + while (b2) + { + Square to = pop_lsb(&b2); + *moveList++ = make_move(to - Up - Up, to); + } + } + + // Promotions and underpromotions + if (pawnsOn7) + { + if (Type == CAPTURES) + emptySquares = ~pos.pieces(); + + if (Type == EVASIONS) + emptySquares &= target; + + Bitboard b1 = shift(pawnsOn7) & enemies; + Bitboard b2 = shift(pawnsOn7) & enemies; + Bitboard b3 = shift(pawnsOn7) & emptySquares; + + while (b1) + moveList = make_promotions(moveList, pop_lsb(&b1), ksq); + + while (b2) + moveList = make_promotions(moveList, pop_lsb(&b2), ksq); + + while (b3) + moveList = make_promotions(moveList, pop_lsb(&b3), ksq); + } + + // Standard and en-passant captures + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + { + Bitboard b1 = shift(pawnsNotOn7) & enemies; + Bitboard b2 = shift(pawnsNotOn7) & enemies; + + while (b1) + { + Square to = pop_lsb(&b1); + *moveList++ = make_move(to - UpRight, to); + } + + while (b2) + { + Square to = pop_lsb(&b2); + *moveList++ = make_move(to - UpLeft, to); + } + + if (pos.ep_square() != SQ_NONE) + { + assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6)); + + // An en passant capture can be an evasion only if the checking piece + // is the double pushed pawn and so is in the target. Otherwise this + // is a discovery check and we are forced to do otherwise. + if (Type == EVASIONS && !(target & (pos.ep_square() - Up))) + return moveList; + + b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square()); + + assert(b1); + + while (b1) + *moveList++ = make(pop_lsb(&b1), pos.ep_square()); + } + } + + return moveList; + } + + + template + ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { + + static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); + + const Square* pl = pos.squares(Us); + + for (Square from = *pl; from != SQ_NONE; from = *++pl) + { + if (Checks) + { + if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN) + && !(attacks_bb(from) & target & pos.check_squares(Pt))) + continue; + + if (pos.blockers_for_king(~Us) & from) + continue; + } + + Bitboard b = attacks_bb(from, pos.pieces()) & target; + + if (Checks) + b &= pos.check_squares(Pt); + + while (b) + *moveList++ = make_move(from, pop_lsb(&b)); + } + + return moveList; + } + + + template + ExtMove* generate_all(const Position& pos, ExtMove* moveList) { + constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations + Bitboard target; + + switch (Type) + { + case CAPTURES: + target = pos.pieces(~Us); + break; + case QUIETS: + case QUIET_CHECKS: + target = ~pos.pieces(); + break; + case EVASIONS: + { + Square checksq = lsb(pos.checkers()); + target = between_bb(pos.square(Us), checksq) | checksq; + break; + } + case NON_EVASIONS: + target = ~pos.pieces(Us); + break; + default: + static_assert(true, "Unsupported type in generate_all()"); + } + + moveList = generate_pawn_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + + if (Type != QUIET_CHECKS && Type != EVASIONS) + { + Square ksq = pos.square(Us); + Bitboard b = attacks_bb(ksq) & target; + while (b) + *moveList++ = make_move(ksq, pop_lsb(&b)); + + if ((Type != CAPTURES) && pos.can_castle(Us & ANY_CASTLING)) + for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) + if (!pos.castling_impeded(cr) && pos.can_castle(cr)) + *moveList++ = make(ksq, pos.castling_rook_square(cr)); + } + + return moveList; + } + +} // namespace + + +/// Generates all pseudo-legal captures plus queen and checking knight promotions +/// Generates all pseudo-legal non-captures and underpromotions(except checking knight) +/// Generates all pseudo-legal captures and non-captures +/// +/// Returns a pointer to the end of the move list. + +template +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + static_assert(Type == CAPTURES || Type == QUIETS || Type == NON_EVASIONS, "Unsupported type in generate()"); + assert(!pos.checkers()); + + Color us = pos.side_to_move(); + + return us == WHITE ? generate_all(pos, moveList) + : generate_all(pos, moveList); +} + +// Explicit template instantiations +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); + + +/// generate generates all pseudo-legal non-captures. +/// Returns a pointer to the end of the move list. +template<> +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + assert(!pos.checkers()); + + Color us = pos.side_to_move(); + Bitboard dc = pos.blockers_for_king(~us) & pos.pieces(us) & ~pos.pieces(PAWN); + + while (dc) + { + Square from = pop_lsb(&dc); + PieceType pt = type_of(pos.piece_on(from)); + + Bitboard b = attacks_bb(pt, from, pos.pieces()) & ~pos.pieces(); + + if (pt == KING) + b &= ~attacks_bb(pos.square(~us)); + + while (b) + *moveList++ = make_move(from, pop_lsb(&b)); + } + + return us == WHITE ? generate_all(pos, moveList) + : generate_all(pos, moveList); +} + + +/// generate generates all pseudo-legal check evasions when the side +/// to move is in check. Returns a pointer to the end of the move list. +template<> +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + assert(pos.checkers()); + + Color us = pos.side_to_move(); + Square ksq = pos.square(us); + Bitboard sliderAttacks = 0; + Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN); + + // Find all the squares attacked by slider checkers. We will remove them from + // the king evasions in order to skip known illegal moves, which avoids any + // useless legality checks later on. + while (sliders) + sliderAttacks |= line_bb(ksq, pop_lsb(&sliders)) & ~pos.checkers(); + + // Generate evasions for king, capture and non capture moves + Bitboard b = attacks_bb(ksq) & ~pos.pieces(us) & ~sliderAttacks; + while (b) + *moveList++ = make_move(ksq, pop_lsb(&b)); + + if (more_than_one(pos.checkers())) + return moveList; // Double check, only a king move can save the day + + // Generate blocking evasions or captures of the checking piece + return us == WHITE ? generate_all(pos, moveList) + : generate_all(pos, moveList); +} + + +/// generate generates all the legal moves in the given position + +template<> +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + Color us = pos.side_to_move(); + Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us); + Square ksq = pos.square(us); + ExtMove* cur = moveList; + + moveList = pos.checkers() ? generate(pos, moveList) + : generate(pos, moveList); + while (cur != moveList) + if ( (pinned || from_sq(*cur) == ksq || type_of(*cur) == ENPASSANT) + && !pos.legal(*cur)) + *cur = (--moveList)->move; + else + ++cur; + + return moveList; +} diff --git a/engines/stockfish13/movegen.h b/engines/stockfish13/movegen.h new file mode 100644 index 0000000..fb616d0 --- /dev/null +++ b/engines/stockfish13/movegen.h @@ -0,0 +1,73 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MOVEGEN_H_INCLUDED +#define MOVEGEN_H_INCLUDED + +#include + +#include "types.h" + +class Position; + +enum GenType { + CAPTURES, + QUIETS, + QUIET_CHECKS, + EVASIONS, + NON_EVASIONS, + LEGAL +}; + +struct ExtMove { + Move move; + int value; + + operator Move() const { return move; } + void operator=(Move m) { move = m; } + + // Inhibit unwanted implicit conversions to Move + // with an ambiguity that yields to a compile error. + operator float() const = delete; +}; + +inline bool operator<(const ExtMove& f, const ExtMove& s) { + return f.value < s.value; +} + +template +ExtMove* generate(const Position& pos, ExtMove* moveList); + +/// The MoveList struct is a simple wrapper around generate(). It sometimes comes +/// in handy to use this class instead of the low level generate() function. +template +struct MoveList { + + explicit MoveList(const Position& pos) : last(generate(pos, moveList)) {} + const ExtMove* begin() const { return moveList; } + const ExtMove* end() const { return last; } + size_t size() const { return last - moveList; } + bool contains(Move move) const { + return std::find(begin(), end(), move) != end(); + } + +private: + ExtMove moveList[MAX_MOVES], *last; +}; + +#endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/engines/stockfish13/movepick.cpp b/engines/stockfish13/movepick.cpp new file mode 100644 index 0000000..153d323 --- /dev/null +++ b/engines/stockfish13/movepick.cpp @@ -0,0 +1,264 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "movepick.h" + +namespace { + + enum Stages { + MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, REFUTATION, QUIET_INIT, QUIET, BAD_CAPTURE, + EVASION_TT, EVASION_INIT, EVASION, + PROBCUT_TT, PROBCUT_INIT, PROBCUT, + QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK + }; + + // partial_insertion_sort() sorts moves in descending order up to and including + // a given limit. The order of moves smaller than the limit is left unspecified. + void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { + + for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) + if (p->value >= limit) + { + ExtMove tmp = *p, *q; + *p = *++sortedEnd; + for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q) + *q = *(q - 1); + *q = tmp; + } + } + +} // namespace + + +/// Constructors of the MovePicker class. As arguments we pass information +/// to help it to return the (presumably) good moves first, to decide which +/// moves to return (in the quiescence search, for instance, we only want to +/// search captures, promotions, and some checks) and how important good move +/// ordering is at the current node. + +/// MovePicker constructor for the main search +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const LowPlyHistory* lp, + const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, const Move* killers, int pl) + : pos(p), mainHistory(mh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch), + ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d), ply(pl) { + + assert(d > 0); + + stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + + !(ttm && pos.pseudo_legal(ttm)); +} + +/// MovePicker constructor for quiescence search +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, + const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs) + : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) { + + assert(d <= 0); + + stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + + !(ttm && (depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare) + && pos.pseudo_legal(ttm)); +} + +/// MovePicker constructor for ProbCut: we generate captures with SEE greater +/// than or equal to the given threshold. +MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) + : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) { + + assert(!pos.checkers()); + + stage = PROBCUT_TT + !(ttm && pos.capture(ttm) + && pos.pseudo_legal(ttm) + && pos.see_ge(ttm, threshold)); +} + +/// MovePicker::score() assigns a numerical value to each move in a list, used +/// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring +/// captures with a good history. Quiets moves are ordered using the histories. +template +void MovePicker::score() { + + static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); + + for (auto& m : *this) + if (Type == CAPTURES) + m.value = int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6 + + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; + + else if (Type == QUIETS) + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] + + 2 * (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] + + 2 * (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] + + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] + + (ply < MAX_LPH ? std::min(4, depth / 3) * (*lowPlyHistory)[ply][from_to(m)] : 0); + + else // Type == EVASIONS + { + if (pos.capture(m)) + m.value = PieceValue[MG][pos.piece_on(to_sq(m))] + - Value(type_of(pos.moved_piece(m))); + else + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] + - (1 << 28); + } +} + +/// MovePicker::select() returns the next move satisfying a predicate function. +/// It never returns the TT move. +template +Move MovePicker::select(Pred filter) { + + while (cur < endMoves) + { + if (T == Best) + std::swap(*cur, *std::max_element(cur, endMoves)); + + if (*cur != ttMove && filter()) + return *cur++; + + cur++; + } + return MOVE_NONE; +} + +/// MovePicker::next_move() is the most important method of the MovePicker class. It +/// returns a new pseudo legal move every time it is called until there are no more +/// moves left, picking the move with the highest score from a list of generated moves. +Move MovePicker::next_move(bool skipQuiets) { + +top: + switch (stage) { + + case MAIN_TT: + case EVASION_TT: + case QSEARCH_TT: + case PROBCUT_TT: + ++stage; + return ttMove; + + case CAPTURE_INIT: + case PROBCUT_INIT: + case QCAPTURE_INIT: + cur = endBadCaptures = moves; + endMoves = generate(pos, cur); + + score(); + ++stage; + goto top; + + case GOOD_CAPTURE: + if (select([&](){ + return pos.see_ge(*cur, Value(-69 * cur->value / 1024)) ? + // Move losing capture to endBadCaptures to be tried later + true : (*endBadCaptures++ = *cur, false); })) + return *(cur - 1); + + // Prepare the pointers to loop over the refutations array + cur = std::begin(refutations); + endMoves = std::end(refutations); + + // If the countermove is the same as a killer, skip it + if ( refutations[0].move == refutations[2].move + || refutations[1].move == refutations[2].move) + --endMoves; + + ++stage; + [[fallthrough]]; + + case REFUTATION: + if (select([&](){ return *cur != MOVE_NONE + && !pos.capture(*cur) + && pos.pseudo_legal(*cur); })) + return *(cur - 1); + ++stage; + [[fallthrough]]; + + case QUIET_INIT: + if (!skipQuiets) + { + cur = endBadCaptures; + endMoves = generate(pos, cur); + + score(); + partial_insertion_sort(cur, endMoves, -3000 * depth); + } + + ++stage; + [[fallthrough]]; + + case QUIET: + if ( !skipQuiets + && select([&](){return *cur != refutations[0].move + && *cur != refutations[1].move + && *cur != refutations[2].move;})) + return *(cur - 1); + + // Prepare the pointers to loop over the bad captures + cur = moves; + endMoves = endBadCaptures; + + ++stage; + [[fallthrough]]; + + case BAD_CAPTURE: + return select([](){ return true; }); + + case EVASION_INIT: + cur = moves; + endMoves = generate(pos, cur); + + score(); + ++stage; + [[fallthrough]]; + + case EVASION: + return select([](){ return true; }); + + case PROBCUT: + return select([&](){ return pos.see_ge(*cur, threshold); }); + + case QCAPTURE: + if (select([&](){ return depth > DEPTH_QS_RECAPTURES + || to_sq(*cur) == recaptureSquare; })) + return *(cur - 1); + + // If we did not find any move and we do not try checks, we have finished + if (depth != DEPTH_QS_CHECKS) + return MOVE_NONE; + + ++stage; + [[fallthrough]]; + + case QCHECK_INIT: + cur = moves; + endMoves = generate(pos, cur); + + ++stage; + [[fallthrough]]; + + case QCHECK: + return select([](){ return true; }); + } + + assert(false); + return MOVE_NONE; // Silence warning +} diff --git a/engines/stockfish13/movepick.h b/engines/stockfish13/movepick.h new file mode 100644 index 0000000..4c0ad55 --- /dev/null +++ b/engines/stockfish13/movepick.h @@ -0,0 +1,159 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MOVEPICK_H_INCLUDED +#define MOVEPICK_H_INCLUDED + +#include +#include +#include + +#include "movegen.h" +#include "position.h" +#include "types.h" + +/// StatsEntry stores the stat table value. It is usually a number but could +/// be a move or even a nested history. We use a class instead of naked value +/// to directly call history update operator<<() on the entry so to use stats +/// tables at caller sites as simple multi-dim arrays. +template +class StatsEntry { + + T entry; + +public: + void operator=(const T& v) { entry = v; } + T* operator&() { return &entry; } + T* operator->() { return &entry; } + operator const T&() const { return entry; } + + void operator<<(int bonus) { + assert(abs(bonus) <= D); // Ensure range is [-D, D] + static_assert(D <= std::numeric_limits::max(), "D overflows T"); + + entry += bonus - entry * abs(bonus) / D; + + assert(abs(entry) <= D); + } +}; + +/// Stats is a generic N-dimensional array used to store various statistics. +/// The first template parameter T is the base type of the array, the second +/// template parameter D limits the range of updates in [-D, D] when we update +/// values with the << operator, while the last parameters (Size and Sizes) +/// encode the dimensions of the array. +template +struct Stats : public std::array, Size> +{ + typedef Stats stats; + + void fill(const T& v) { + + // For standard-layout 'this' points to first struct member + assert(std::is_standard_layout::value); + + typedef StatsEntry entry; + entry* p = reinterpret_cast(this); + std::fill(p, p + sizeof(*this) / sizeof(entry), v); + } +}; + +template +struct Stats : public std::array, Size> {}; + +/// In stats table, D=0 means that the template parameter is not used +enum StatsParams { NOT_USED = 0 }; +enum StatsType { NoCaptures, Captures }; + +/// ButterflyHistory records how often quiet moves have been successful or +/// unsuccessful during the current search, and is used for reduction and move +/// ordering decisions. It uses 2 tables (one for each color) indexed by +/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards +typedef Stats ButterflyHistory; + +/// At higher depths LowPlyHistory records successful quiet moves near the root +/// and quiet moves which are/were in the PV (ttPv). It is cleared with each new +/// search and filled during iterative deepening. +constexpr int MAX_LPH = 4; +typedef Stats LowPlyHistory; + +/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous +/// move, see www.chessprogramming.org/Countermove_Heuristic +typedef Stats CounterMoveHistory; + +/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] +typedef Stats CapturePieceToHistory; + +/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] +typedef Stats PieceToHistory; + +/// ContinuationHistory is the combined history of a given pair of moves, usually +/// the current one given a previous one. The nested history table is based on +/// PieceToHistory instead of ButterflyBoards. +typedef Stats ContinuationHistory; + + +/// MovePicker class is used to pick one pseudo legal move at a time from the +/// current position. The most important method is next_move(), which returns a +/// new pseudo legal move each time it is called, until there are no moves left, +/// when MOVE_NONE is returned. In order to improve the efficiency of the alpha +/// beta algorithm, MovePicker attempts to return the moves which are most likely +/// to get a cut-off first. +class MovePicker { + + enum PickType { Next, Best }; + +public: + MovePicker(const MovePicker&) = delete; + MovePicker& operator=(const MovePicker&) = delete; + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + MovePicker(const Position&, Move, Depth, const ButterflyHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + Square); + MovePicker(const Position&, Move, Depth, const ButterflyHistory*, + const LowPlyHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + Move, + const Move*, + int); + Move next_move(bool skipQuiets = false); + +private: + template Move select(Pred); + template void score(); + ExtMove* begin() { return cur; } + ExtMove* end() { return endMoves; } + + const Position& pos; + const ButterflyHistory* mainHistory; + const LowPlyHistory* lowPlyHistory; + const CapturePieceToHistory* captureHistory; + const PieceToHistory** continuationHistory; + Move ttMove; + ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; + int stage; + Square recaptureSquare; + Value threshold; + Depth depth; + int ply; + ExtMove moves[MAX_MOVES]; +}; + +#endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/engines/stockfish/nnue/architectures/halfkp_256x2-32-32.h b/engines/stockfish13/nnue/architectures/halfkp_256x2-32-32.h similarity index 100% rename from engines/stockfish/nnue/architectures/halfkp_256x2-32-32.h rename to engines/stockfish13/nnue/architectures/halfkp_256x2-32-32.h diff --git a/engines/stockfish13/nnue/evaluate_nnue.cpp b/engines/stockfish13/nnue/evaluate_nnue.cpp new file mode 100644 index 0000000..72d1820 --- /dev/null +++ b/engines/stockfish13/nnue/evaluate_nnue.cpp @@ -0,0 +1,146 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Code for calculating NNUE evaluation function + +#include +#include + +#include "../evaluate.h" +#include "../position.h" +#include "../misc.h" +#include "../uci.h" + +#include "evaluate_nnue.h" + +namespace Eval::NNUE { + + uint32_t kpp_board_index[PIECE_NB][COLOR_NB] = { + // convention: W - us, B - them + // viewed from other side, W and B are reversed + { PS_NONE, PS_NONE }, + { PS_W_PAWN, PS_B_PAWN }, + { PS_W_KNIGHT, PS_B_KNIGHT }, + { PS_W_BISHOP, PS_B_BISHOP }, + { PS_W_ROOK, PS_B_ROOK }, + { PS_W_QUEEN, PS_B_QUEEN }, + { PS_W_KING, PS_B_KING }, + { PS_NONE, PS_NONE }, + { PS_NONE, PS_NONE }, + { PS_B_PAWN, PS_W_PAWN }, + { PS_B_KNIGHT, PS_W_KNIGHT }, + { PS_B_BISHOP, PS_W_BISHOP }, + { PS_B_ROOK, PS_W_ROOK }, + { PS_B_QUEEN, PS_W_QUEEN }, + { PS_B_KING, PS_W_KING }, + { PS_NONE, PS_NONE } + }; + + // Input feature converter + LargePagePtr feature_transformer; + + // Evaluation function + AlignedPtr network; + + // Evaluation function file name + std::string fileName; + + namespace Detail { + + // Initialize the evaluation function parameters + template + void Initialize(AlignedPtr& pointer) { + + pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); + std::memset(pointer.get(), 0, sizeof(T)); + } + + template + void Initialize(LargePagePtr& pointer) { + + static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); + std::memset(pointer.get(), 0, sizeof(T)); + } + + // Read evaluation function parameters + template + bool ReadParameters(std::istream& stream, T& reference) { + + std::uint32_t header; + header = read_little_endian(stream); + if (!stream || header != T::GetHashValue()) return false; + return reference.ReadParameters(stream); + } + + } // namespace Detail + + // Initialize the evaluation function parameters + void Initialize() { + + Detail::Initialize(feature_transformer); + Detail::Initialize(network); + } + + // Read network header + bool ReadHeader(std::istream& stream, std::uint32_t* hash_value, std::string* architecture) + { + std::uint32_t version, size; + + version = read_little_endian(stream); + *hash_value = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != kVersion) return false; + architecture->resize(size); + stream.read(&(*architecture)[0], size); + return !stream.fail(); + } + + // Read network parameters + bool ReadParameters(std::istream& stream) { + + std::uint32_t hash_value; + std::string architecture; + if (!ReadHeader(stream, &hash_value, &architecture)) return false; + if (hash_value != kHashValue) return false; + if (!Detail::ReadParameters(stream, *feature_transformer)) return false; + if (!Detail::ReadParameters(stream, *network)) return false; + return stream && stream.peek() == std::ios::traits_type::eof(); + } + + // Evaluation function. Perform differential calculation. + Value evaluate(const Position& pos) { + + alignas(kCacheLineSize) TransformedFeatureType + transformed_features[FeatureTransformer::kBufferSize]; + feature_transformer->Transform(pos, transformed_features); + alignas(kCacheLineSize) char buffer[Network::kBufferSize]; + const auto output = network->Propagate(transformed_features, buffer); + + return static_cast(output[0] / FV_SCALE); + } + + // Load eval, from a file stream or a memory stream + bool load_eval(std::string streamName, std::istream& stream) { + + Initialize(); + fileName = streamName; + return ReadParameters(stream); + } + +} // namespace Eval::NNUE diff --git a/engines/stockfish13/nnue/evaluate_nnue.h b/engines/stockfish13/nnue/evaluate_nnue.h new file mode 100644 index 0000000..459a93d --- /dev/null +++ b/engines/stockfish13/nnue/evaluate_nnue.h @@ -0,0 +1,59 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// header used in NNUE evaluation function + +#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED +#define NNUE_EVALUATE_NNUE_H_INCLUDED + +#include "nnue_feature_transformer.h" + +#include + +namespace Eval::NNUE { + + // Hash value of evaluation function structure + constexpr std::uint32_t kHashValue = + FeatureTransformer::GetHashValue() ^ Network::GetHashValue(); + + // Deleter for automating release of memory area + template + struct AlignedDeleter { + void operator()(T* ptr) const { + ptr->~T(); + std_aligned_free(ptr); + } + }; + + template + struct TtmemDeleter { + void operator()(T* ptr) const { + ptr->~T(); + aligned_large_pages_free(ptr); + } + }; + + template + using AlignedPtr = std::unique_ptr>; + + template + using LargePagePtr = std::unique_ptr>; + +} // namespace Eval::NNUE + +#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/engines/stockfish/nnue/features/feature_set.h b/engines/stockfish13/nnue/features/feature_set.h similarity index 100% rename from engines/stockfish/nnue/features/feature_set.h rename to engines/stockfish13/nnue/features/feature_set.h diff --git a/engines/stockfish/nnue/features/features_common.h b/engines/stockfish13/nnue/features/features_common.h similarity index 100% rename from engines/stockfish/nnue/features/features_common.h rename to engines/stockfish13/nnue/features/features_common.h diff --git a/engines/stockfish/nnue/features/half_kp.cpp b/engines/stockfish13/nnue/features/half_kp.cpp similarity index 100% rename from engines/stockfish/nnue/features/half_kp.cpp rename to engines/stockfish13/nnue/features/half_kp.cpp diff --git a/engines/stockfish/nnue/features/half_kp.h b/engines/stockfish13/nnue/features/half_kp.h similarity index 100% rename from engines/stockfish/nnue/features/half_kp.h rename to engines/stockfish13/nnue/features/half_kp.h diff --git a/engines/stockfish/nnue/features/index_list.h b/engines/stockfish13/nnue/features/index_list.h similarity index 100% rename from engines/stockfish/nnue/features/index_list.h rename to engines/stockfish13/nnue/features/index_list.h diff --git a/engines/stockfish13/nnue/layers/affine_transform.h b/engines/stockfish13/nnue/layers/affine_transform.h new file mode 100644 index 0000000..94d0b5a --- /dev/null +++ b/engines/stockfish13/nnue/layers/affine_transform.h @@ -0,0 +1,266 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer AffineTransform of NNUE evaluation function + +#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED + +#include +#include "../nnue_common.h" + +namespace Eval::NNUE::Layers { + + // Affine transformation layer + template + class AffineTransform { + public: + // Input/output type + using InputType = typename PreviousLayer::OutputType; + using OutputType = std::int32_t; + static_assert(std::is_same::value, ""); + + // Number of input/output dimensions + static constexpr IndexType kInputDimensions = + PreviousLayer::kOutputDimensions; + static constexpr IndexType kOutputDimensions = OutputDimensions; + static constexpr IndexType kPaddedInputDimensions = + CeilToMultiple(kInputDimensions, kMaxSimdWidth); + + // Size of forward propagation buffer used in this layer + static constexpr std::size_t kSelfBufferSize = + CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize); + + // Size of the forward propagation buffer used from the input layer to this layer + static constexpr std::size_t kBufferSize = + PreviousLayer::kBufferSize + kSelfBufferSize; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t GetHashValue() { + std::uint32_t hash_value = 0xCC03DAE4u; + hash_value += kOutputDimensions; + hash_value ^= PreviousLayer::GetHashValue() >> 1; + hash_value ^= PreviousLayer::GetHashValue() << 31; + return hash_value; + } + + // Read network parameters + bool ReadParameters(std::istream& stream) { + if (!previous_layer_.ReadParameters(stream)) return false; + for (std::size_t i = 0; i < kOutputDimensions; ++i) + biases_[i] = read_little_endian(stream); + for (std::size_t i = 0; i < kOutputDimensions * kPaddedInputDimensions; ++i) + weights_[i] = read_little_endian(stream); + return !stream.fail(); + } + + // Forward propagation + const OutputType* Propagate( + const TransformedFeatureType* transformed_features, char* buffer) const { + const auto input = previous_layer_.Propagate( + transformed_features, buffer + kSelfBufferSize); + const auto output = reinterpret_cast(buffer); + + #if defined(USE_AVX512) + constexpr IndexType kNumChunks = kPaddedInputDimensions / (kSimdWidth * 2); + const auto input_vector = reinterpret_cast(input); + #if !defined(USE_VNNI) + const __m512i kOnes = _mm512_set1_epi16(1); + #endif + + #elif defined(USE_AVX2) + constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; + const auto input_vector = reinterpret_cast(input); + #if !defined(USE_VNNI) + const __m256i kOnes = _mm256_set1_epi16(1); + #endif + + #elif defined(USE_SSE2) + constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; + #ifndef USE_SSSE3 + const __m128i kZeros = _mm_setzero_si128(); + #else + const __m128i kOnes = _mm_set1_epi16(1); + #endif + const auto input_vector = reinterpret_cast(input); + + #elif defined(USE_MMX) + constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; + const __m64 kZeros = _mm_setzero_si64(); + const auto input_vector = reinterpret_cast(input); + + #elif defined(USE_NEON) + constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; + const auto input_vector = reinterpret_cast(input); + #endif + + for (IndexType i = 0; i < kOutputDimensions; ++i) { + const IndexType offset = i * kPaddedInputDimensions; + + #if defined(USE_AVX512) + __m512i sum = _mm512_setzero_si512(); + const auto row = reinterpret_cast(&weights_[offset]); + for (IndexType j = 0; j < kNumChunks; ++j) { + #if defined(USE_VNNI) + sum = _mm512_dpbusd_epi32(sum, _mm512_loadA_si512(&input_vector[j]), _mm512_load_si512(&row[j])); + #else + __m512i product = _mm512_maddubs_epi16(_mm512_loadA_si512(&input_vector[j]), _mm512_load_si512(&row[j])); + product = _mm512_madd_epi16(product, kOnes); + sum = _mm512_add_epi32(sum, product); + #endif + } + + // Note: Changing kMaxSimdWidth from 32 to 64 breaks loading existing networks. + // As a result kPaddedInputDimensions may not be an even multiple of 64(512bit) + // and we have to do one more 256bit chunk. + if (kPaddedInputDimensions != kNumChunks * kSimdWidth * 2) + { + const auto iv256 = reinterpret_cast(&input_vector[kNumChunks]); + const auto row256 = reinterpret_cast(&row[kNumChunks]); + #if defined(USE_VNNI) + __m256i product256 = _mm256_dpbusd_epi32( + _mm512_castsi512_si256(sum), _mm256_loadA_si256(&iv256[0]), _mm256_load_si256(&row256[0])); + sum = _mm512_inserti32x8(sum, product256, 0); + #else + __m256i product256 = _mm256_maddubs_epi16(_mm256_loadA_si256(&iv256[0]), _mm256_load_si256(&row256[0])); + sum = _mm512_add_epi32(sum, _mm512_cvtepi16_epi32(product256)); + #endif + } + output[i] = _mm512_reduce_add_epi32(sum) + biases_[i]; + + #elif defined(USE_AVX2) + __m256i sum = _mm256_setzero_si256(); + const auto row = reinterpret_cast(&weights_[offset]); + for (IndexType j = 0; j < kNumChunks; ++j) { + #if defined(USE_VNNI) + sum = _mm256_dpbusd_epi32(sum, _mm256_loadA_si256(&input_vector[j]), _mm256_load_si256(&row[j])); + #else + __m256i product = _mm256_maddubs_epi16(_mm256_loadA_si256(&input_vector[j]), _mm256_load_si256(&row[j])); + product = _mm256_madd_epi16(product, kOnes); + sum = _mm256_add_epi32(sum, product); + #endif + } + __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); + output[i] = _mm_cvtsi128_si32(sum128) + biases_[i]; + + #elif defined(USE_SSSE3) + __m128i sum = _mm_setzero_si128(); + const auto row = reinterpret_cast(&weights_[offset]); + for (int j = 0; j < (int)kNumChunks - 1; j += 2) { + __m128i product0 = _mm_maddubs_epi16(_mm_load_si128(&input_vector[j]), _mm_load_si128(&row[j])); + product0 = _mm_madd_epi16(product0, kOnes); + sum = _mm_add_epi32(sum, product0); + __m128i product1 = _mm_maddubs_epi16(_mm_load_si128(&input_vector[j+1]), _mm_load_si128(&row[j+1])); + product1 = _mm_madd_epi16(product1, kOnes); + sum = _mm_add_epi32(sum, product1); + } + if (kNumChunks & 0x1) { + __m128i product = _mm_maddubs_epi16(_mm_load_si128(&input_vector[kNumChunks-1]), _mm_load_si128(&row[kNumChunks-1])); + product = _mm_madd_epi16(product, kOnes); + sum = _mm_add_epi32(sum, product); + } + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB + output[i] = _mm_cvtsi128_si32(sum) + biases_[i]; + + #elif defined(USE_SSE2) + __m128i sum_lo = _mm_cvtsi32_si128(biases_[i]); + __m128i sum_hi = kZeros; + const auto row = reinterpret_cast(&weights_[offset]); + for (IndexType j = 0; j < kNumChunks; ++j) { + __m128i row_j = _mm_load_si128(&row[j]); + __m128i input_j = _mm_load_si128(&input_vector[j]); + __m128i row_signs = _mm_cmpgt_epi8(kZeros, row_j); + __m128i extended_row_lo = _mm_unpacklo_epi8(row_j, row_signs); + __m128i extended_row_hi = _mm_unpackhi_epi8(row_j, row_signs); + __m128i extended_input_lo = _mm_unpacklo_epi8(input_j, kZeros); + __m128i extended_input_hi = _mm_unpackhi_epi8(input_j, kZeros); + __m128i product_lo = _mm_madd_epi16(extended_row_lo, extended_input_lo); + __m128i product_hi = _mm_madd_epi16(extended_row_hi, extended_input_hi); + sum_lo = _mm_add_epi32(sum_lo, product_lo); + sum_hi = _mm_add_epi32(sum_hi, product_hi); + } + __m128i sum = _mm_add_epi32(sum_lo, sum_hi); + __m128i sum_high_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sum_high_64); + __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sum_second_32); + output[i] = _mm_cvtsi128_si32(sum); + + #elif defined(USE_MMX) + __m64 sum_lo = _mm_cvtsi32_si64(biases_[i]); + __m64 sum_hi = kZeros; + const auto row = reinterpret_cast(&weights_[offset]); + for (IndexType j = 0; j < kNumChunks; ++j) { + __m64 row_j = row[j]; + __m64 input_j = input_vector[j]; + __m64 row_signs = _mm_cmpgt_pi8(kZeros, row_j); + __m64 extended_row_lo = _mm_unpacklo_pi8(row_j, row_signs); + __m64 extended_row_hi = _mm_unpackhi_pi8(row_j, row_signs); + __m64 extended_input_lo = _mm_unpacklo_pi8(input_j, kZeros); + __m64 extended_input_hi = _mm_unpackhi_pi8(input_j, kZeros); + __m64 product_lo = _mm_madd_pi16(extended_row_lo, extended_input_lo); + __m64 product_hi = _mm_madd_pi16(extended_row_hi, extended_input_hi); + sum_lo = _mm_add_pi32(sum_lo, product_lo); + sum_hi = _mm_add_pi32(sum_hi, product_hi); + } + __m64 sum = _mm_add_pi32(sum_lo, sum_hi); + sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum)); + output[i] = _mm_cvtsi64_si32(sum); + + #elif defined(USE_NEON) + int32x4_t sum = {biases_[i]}; + const auto row = reinterpret_cast(&weights_[offset]); + for (IndexType j = 0; j < kNumChunks; ++j) { + int16x8_t product = vmull_s8(input_vector[j * 2], row[j * 2]); + product = vmlal_s8(product, input_vector[j * 2 + 1], row[j * 2 + 1]); + sum = vpadalq_s16(sum, product); + } + output[i] = sum[0] + sum[1] + sum[2] + sum[3]; + + #else + OutputType sum = biases_[i]; + for (IndexType j = 0; j < kInputDimensions; ++j) { + sum += weights_[offset + j] * input[j]; + } + output[i] = sum; + #endif + + } + #if defined(USE_MMX) + _mm_empty(); + #endif + return output; + } + + private: + using BiasType = OutputType; + using WeightType = std::int8_t; + + PreviousLayer previous_layer_; + + alignas(kCacheLineSize) BiasType biases_[kOutputDimensions]; + alignas(kCacheLineSize) + WeightType weights_[kOutputDimensions * kPaddedInputDimensions]; + }; + +} // namespace Eval::NNUE::Layers + +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED diff --git a/engines/stockfish13/nnue/layers/clipped_relu.h b/engines/stockfish13/nnue/layers/clipped_relu.h new file mode 100644 index 0000000..44d8a7d --- /dev/null +++ b/engines/stockfish13/nnue/layers/clipped_relu.h @@ -0,0 +1,166 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer ClippedReLU of NNUE evaluation function + +#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED +#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED + +#include "../nnue_common.h" + +namespace Eval::NNUE::Layers { + + // Clipped ReLU + template + class ClippedReLU { + public: + // Input/output type + using InputType = typename PreviousLayer::OutputType; + using OutputType = std::uint8_t; + static_assert(std::is_same::value, ""); + + // Number of input/output dimensions + static constexpr IndexType kInputDimensions = + PreviousLayer::kOutputDimensions; + static constexpr IndexType kOutputDimensions = kInputDimensions; + + // Size of forward propagation buffer used in this layer + static constexpr std::size_t kSelfBufferSize = + CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize); + + // Size of the forward propagation buffer used from the input layer to this layer + static constexpr std::size_t kBufferSize = + PreviousLayer::kBufferSize + kSelfBufferSize; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t GetHashValue() { + std::uint32_t hash_value = 0x538D24C7u; + hash_value += PreviousLayer::GetHashValue(); + return hash_value; + } + + // Read network parameters + bool ReadParameters(std::istream& stream) { + return previous_layer_.ReadParameters(stream); + } + + // Forward propagation + const OutputType* Propagate( + const TransformedFeatureType* transformed_features, char* buffer) const { + const auto input = previous_layer_.Propagate( + transformed_features, buffer + kSelfBufferSize); + const auto output = reinterpret_cast(buffer); + + #if defined(USE_AVX2) + constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth; + const __m256i kZero = _mm256_setzero_si256(); + const __m256i kOffsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m256i*>(output); + for (IndexType i = 0; i < kNumChunks; ++i) { + const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32( + _mm256_loadA_si256(&in[i * 4 + 0]), + _mm256_loadA_si256(&in[i * 4 + 1])), kWeightScaleBits); + const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32( + _mm256_loadA_si256(&in[i * 4 + 2]), + _mm256_loadA_si256(&in[i * 4 + 3])), kWeightScaleBits); + _mm256_storeA_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8( + _mm256_packs_epi16(words0, words1), kZero), kOffsets)); + } + constexpr IndexType kStart = kNumChunks * kSimdWidth; + + #elif defined(USE_SSE2) + constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth; + + #ifdef USE_SSE41 + const __m128i kZero = _mm_setzero_si128(); + #else + const __m128i k0x80s = _mm_set1_epi8(-128); + #endif + + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < kNumChunks; ++i) { + const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( + _mm_load_si128(&in[i * 4 + 0]), + _mm_load_si128(&in[i * 4 + 1])), kWeightScaleBits); + const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( + _mm_load_si128(&in[i * 4 + 2]), + _mm_load_si128(&in[i * 4 + 3])), kWeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], + + #ifdef USE_SSE41 + _mm_max_epi8(packedbytes, kZero) + #else + _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) + #endif + + ); + } + constexpr IndexType kStart = kNumChunks * kSimdWidth; + + #elif defined(USE_MMX) + constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth; + const __m64 k0x80s = _mm_set1_pi8(-128); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m64*>(output); + for (IndexType i = 0; i < kNumChunks; ++i) { + const __m64 words0 = _mm_srai_pi16( + _mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]), + kWeightScaleBits); + const __m64 words1 = _mm_srai_pi16( + _mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]), + kWeightScaleBits); + const __m64 packedbytes = _mm_packs_pi16(words0, words1); + out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); + } + _mm_empty(); + constexpr IndexType kStart = kNumChunks * kSimdWidth; + + #elif defined(USE_NEON) + constexpr IndexType kNumChunks = kInputDimensions / (kSimdWidth / 2); + const int8x8_t kZero = {0}; + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast(output); + for (IndexType i = 0; i < kNumChunks; ++i) { + int16x8_t shifted; + const auto pack = reinterpret_cast(&shifted); + pack[0] = vqshrn_n_s32(in[i * 2 + 0], kWeightScaleBits); + pack[1] = vqshrn_n_s32(in[i * 2 + 1], kWeightScaleBits); + out[i] = vmax_s8(vqmovn_s16(shifted), kZero); + } + constexpr IndexType kStart = kNumChunks * (kSimdWidth / 2); + #else + constexpr IndexType kStart = 0; + #endif + + for (IndexType i = kStart; i < kInputDimensions; ++i) { + output[i] = static_cast( + std::max(0, std::min(127, input[i] >> kWeightScaleBits))); + } + return output; + } + + private: + PreviousLayer previous_layer_; + }; + +} // namespace Eval::NNUE::Layers + +#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED diff --git a/engines/stockfish13/nnue/layers/input_slice.h b/engines/stockfish13/nnue/layers/input_slice.h new file mode 100644 index 0000000..afca14c --- /dev/null +++ b/engines/stockfish13/nnue/layers/input_slice.h @@ -0,0 +1,68 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// NNUE evaluation function layer InputSlice definition + +#ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED +#define NNUE_LAYERS_INPUT_SLICE_H_INCLUDED + +#include "../nnue_common.h" + +namespace Eval::NNUE::Layers { + +// Input layer +template +class InputSlice { + public: + // Need to maintain alignment + static_assert(Offset % kMaxSimdWidth == 0, ""); + + // Output type + using OutputType = TransformedFeatureType; + + // Output dimensionality + static constexpr IndexType kOutputDimensions = OutputDimensions; + + // Size of forward propagation buffer used from the input layer to this layer + static constexpr std::size_t kBufferSize = 0; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t GetHashValue() { + std::uint32_t hash_value = 0xEC42E90Du; + hash_value ^= kOutputDimensions ^ (Offset << 10); + return hash_value; + } + + // Read network parameters + bool ReadParameters(std::istream& /*stream*/) { + return true; + } + + // Forward propagation + const OutputType* Propagate( + const TransformedFeatureType* transformed_features, + char* /*buffer*/) const { + return transformed_features + Offset; + } + + private: +}; + +} // namespace Layers + +#endif // #ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED diff --git a/engines/stockfish13/nnue/nnue_accumulator.h b/engines/stockfish13/nnue/nnue_accumulator.h new file mode 100644 index 0000000..2637071 --- /dev/null +++ b/engines/stockfish13/nnue/nnue_accumulator.h @@ -0,0 +1,37 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Class for difference calculation of NNUE evaluation function + +#ifndef NNUE_ACCUMULATOR_H_INCLUDED +#define NNUE_ACCUMULATOR_H_INCLUDED + +#include "nnue_architecture.h" + +namespace Eval::NNUE { + + // Class that holds the result of affine transformation of input features + struct alignas(kCacheLineSize) Accumulator { + std::int16_t + accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions]; + bool computed_accumulation; + }; + +} // namespace Eval::NNUE + +#endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/engines/stockfish13/nnue/nnue_architecture.h b/engines/stockfish13/nnue/nnue_architecture.h new file mode 100644 index 0000000..91cdc4b --- /dev/null +++ b/engines/stockfish13/nnue/nnue_architecture.h @@ -0,0 +1,38 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Input features and network structure used in NNUE evaluation function + +#ifndef NNUE_ARCHITECTURE_H_INCLUDED +#define NNUE_ARCHITECTURE_H_INCLUDED + +// Defines the network structure +#include "architectures/halfkp_256x2-32-32.h" + +namespace Eval::NNUE { + + static_assert(kTransformedFeatureDimensions % kMaxSimdWidth == 0, ""); + static_assert(Network::kOutputDimensions == 1, ""); + static_assert(std::is_same::value, ""); + + // Trigger for full calculation instead of difference calculation + constexpr auto kRefreshTriggers = RawFeatures::kRefreshTriggers; + +} // namespace Eval::NNUE + +#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED diff --git a/engines/stockfish13/nnue/nnue_common.h b/engines/stockfish13/nnue/nnue_common.h new file mode 100644 index 0000000..7bc905d --- /dev/null +++ b/engines/stockfish13/nnue/nnue_common.h @@ -0,0 +1,148 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Constants used in NNUE evaluation function + +#ifndef NNUE_COMMON_H_INCLUDED +#define NNUE_COMMON_H_INCLUDED + +#include +#include + +#if defined(USE_AVX2) +#include + +#elif defined(USE_SSE41) +#include + +#elif defined(USE_SSSE3) +#include + +#elif defined(USE_SSE2) +#include + +#elif defined(USE_MMX) +#include + +#elif defined(USE_NEON) +#include +#endif + +// HACK: Use _mm256_loadu_si256() instead of _mm256_load_si256. Otherwise a binary +// compiled with older g++ crashes because the output memory is not aligned +// even though alignas is specified. +#if defined(USE_AVX2) +#if defined(__GNUC__ ) && (__GNUC__ < 9) && defined(_WIN32) && !defined(__clang__) +#define _mm256_loadA_si256 _mm256_loadu_si256 +#define _mm256_storeA_si256 _mm256_storeu_si256 +#else +#define _mm256_loadA_si256 _mm256_load_si256 +#define _mm256_storeA_si256 _mm256_store_si256 +#endif +#endif + +#if defined(USE_AVX512) +#if defined(__GNUC__ ) && (__GNUC__ < 9) && defined(_WIN32) && !defined(__clang__) +#define _mm512_loadA_si512 _mm512_loadu_si512 +#define _mm512_storeA_si512 _mm512_storeu_si512 +#else +#define _mm512_loadA_si512 _mm512_load_si512 +#define _mm512_storeA_si512 _mm512_store_si512 +#endif +#endif + +namespace Eval::NNUE { + + // Version of the evaluation file + constexpr std::uint32_t kVersion = 0x7AF32F16u; + + // Constant used in evaluation value calculation + constexpr int FV_SCALE = 16; + constexpr int kWeightScaleBits = 6; + + // Size of cache line (in bytes) + constexpr std::size_t kCacheLineSize = 64; + + // SIMD width (in bytes) + #if defined(USE_AVX2) + constexpr std::size_t kSimdWidth = 32; + + #elif defined(USE_SSE2) + constexpr std::size_t kSimdWidth = 16; + + #elif defined(USE_MMX) + constexpr std::size_t kSimdWidth = 8; + + #elif defined(USE_NEON) + constexpr std::size_t kSimdWidth = 16; + #endif + + constexpr std::size_t kMaxSimdWidth = 32; + + // unique number for each piece type on each square + enum { + PS_NONE = 0, + PS_W_PAWN = 1, + PS_B_PAWN = 1 * SQUARE_NB + 1, + PS_W_KNIGHT = 2 * SQUARE_NB + 1, + PS_B_KNIGHT = 3 * SQUARE_NB + 1, + PS_W_BISHOP = 4 * SQUARE_NB + 1, + PS_B_BISHOP = 5 * SQUARE_NB + 1, + PS_W_ROOK = 6 * SQUARE_NB + 1, + PS_B_ROOK = 7 * SQUARE_NB + 1, + PS_W_QUEEN = 8 * SQUARE_NB + 1, + PS_B_QUEEN = 9 * SQUARE_NB + 1, + PS_W_KING = 10 * SQUARE_NB + 1, + PS_END = PS_W_KING, // pieces without kings (pawns included) + PS_B_KING = 11 * SQUARE_NB + 1, + PS_END2 = 12 * SQUARE_NB + 1 + }; + + extern uint32_t kpp_board_index[PIECE_NB][COLOR_NB]; + + // Type of input feature after conversion + using TransformedFeatureType = std::uint8_t; + using IndexType = std::uint32_t; + + // Round n up to be a multiple of base + template + constexpr IntType CeilToMultiple(IntType n, IntType base) { + return (n + base - 1) / base * base; + } + + // read_little_endian() is our utility to read an integer (signed or unsigned, any size) + // from a stream in little-endian order. We swap the byte order after the read if + // necessary to return a result with the byte ordering of the compiling machine. + template + inline IntType read_little_endian(std::istream& stream) { + + IntType result; + std::uint8_t u[sizeof(IntType)]; + typename std::make_unsigned::type v = 0; + + stream.read(reinterpret_cast(u), sizeof(IntType)); + for (std::size_t i = 0; i < sizeof(IntType); ++i) + v = (v << 8) | u[sizeof(IntType) - i - 1]; + + std::memcpy(&result, &v, sizeof(IntType)); + return result; + } + +} // namespace Eval::NNUE + +#endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/engines/stockfish13/nnue/nnue_feature_transformer.h b/engines/stockfish13/nnue/nnue_feature_transformer.h new file mode 100644 index 0000000..e71ee60 --- /dev/null +++ b/engines/stockfish13/nnue/nnue_feature_transformer.h @@ -0,0 +1,387 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// A class that converts the input features of the NNUE evaluation function + +#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED +#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED + +#include "nnue_common.h" +#include "nnue_architecture.h" +#include "features/index_list.h" + +#include // std::memset() + +namespace Eval::NNUE { + + // If vector instructions are enabled, we update and refresh the + // accumulator tile by tile such that each tile fits in the CPU's + // vector registers. + #define TILING + + #ifdef USE_AVX512 + typedef __m512i vec_t; + #define vec_load(a) _mm512_loadA_si512(a) + #define vec_store(a,b) _mm512_storeA_si512(a,b) + #define vec_add_16(a,b) _mm512_add_epi16(a,b) + #define vec_sub_16(a,b) _mm512_sub_epi16(a,b) + static constexpr IndexType kNumRegs = 8; // only 8 are needed + + #elif USE_AVX2 + typedef __m256i vec_t; + #define vec_load(a) _mm256_loadA_si256(a) + #define vec_store(a,b) _mm256_storeA_si256(a,b) + #define vec_add_16(a,b) _mm256_add_epi16(a,b) + #define vec_sub_16(a,b) _mm256_sub_epi16(a,b) + static constexpr IndexType kNumRegs = 16; + + #elif USE_SSE2 + typedef __m128i vec_t; + #define vec_load(a) (*(a)) + #define vec_store(a,b) *(a)=(b) + #define vec_add_16(a,b) _mm_add_epi16(a,b) + #define vec_sub_16(a,b) _mm_sub_epi16(a,b) + static constexpr IndexType kNumRegs = Is64Bit ? 16 : 8; + + #elif USE_MMX + typedef __m64 vec_t; + #define vec_load(a) (*(a)) + #define vec_store(a,b) *(a)=(b) + #define vec_add_16(a,b) _mm_add_pi16(a,b) + #define vec_sub_16(a,b) _mm_sub_pi16(a,b) + static constexpr IndexType kNumRegs = 8; + + #elif USE_NEON + typedef int16x8_t vec_t; + #define vec_load(a) (*(a)) + #define vec_store(a,b) *(a)=(b) + #define vec_add_16(a,b) vaddq_s16(a,b) + #define vec_sub_16(a,b) vsubq_s16(a,b) + static constexpr IndexType kNumRegs = 16; + + #else + #undef TILING + + #endif + + // Input feature converter + class FeatureTransformer { + + private: + // Number of output dimensions for one side + static constexpr IndexType kHalfDimensions = kTransformedFeatureDimensions; + + #ifdef TILING + static constexpr IndexType kTileHeight = kNumRegs * sizeof(vec_t) / 2; + static_assert(kHalfDimensions % kTileHeight == 0, "kTileHeight must divide kHalfDimensions"); + #endif + + public: + // Output type + using OutputType = TransformedFeatureType; + + // Number of input/output dimensions + static constexpr IndexType kInputDimensions = RawFeatures::kDimensions; + static constexpr IndexType kOutputDimensions = kHalfDimensions * 2; + + // Size of forward propagation buffer + static constexpr std::size_t kBufferSize = + kOutputDimensions * sizeof(OutputType); + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t GetHashValue() { + + return RawFeatures::kHashValue ^ kOutputDimensions; + } + + // Read network parameters + bool ReadParameters(std::istream& stream) { + + for (std::size_t i = 0; i < kHalfDimensions; ++i) + biases_[i] = read_little_endian(stream); + for (std::size_t i = 0; i < kHalfDimensions * kInputDimensions; ++i) + weights_[i] = read_little_endian(stream); + return !stream.fail(); + } + + // Proceed with the difference calculation if possible + bool UpdateAccumulatorIfPossible(const Position& pos) const { + + const auto now = pos.state(); + if (now->accumulator.computed_accumulation) + return true; + + const auto prev = now->previous; + if (prev && prev->accumulator.computed_accumulation) { + UpdateAccumulator(pos); + return true; + } + + return false; + } + + // Convert input features + void Transform(const Position& pos, OutputType* output) const { + + if (!UpdateAccumulatorIfPossible(pos)) + RefreshAccumulator(pos); + + const auto& accumulation = pos.state()->accumulator.accumulation; + + #if defined(USE_AVX2) + constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth; + constexpr int kControl = 0b11011000; + const __m256i kZero = _mm256_setzero_si256(); + + #elif defined(USE_SSE2) + constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth; + + #ifdef USE_SSE41 + const __m128i kZero = _mm_setzero_si128(); + #else + const __m128i k0x80s = _mm_set1_epi8(-128); + #endif + + #elif defined(USE_MMX) + constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth; + const __m64 k0x80s = _mm_set1_pi8(-128); + + #elif defined(USE_NEON) + constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2); + const int8x8_t kZero = {0}; + #endif + + const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; + for (IndexType p = 0; p < 2; ++p) { + const IndexType offset = kHalfDimensions * p; + + #if defined(USE_AVX2) + auto out = reinterpret_cast<__m256i*>(&output[offset]); + for (IndexType j = 0; j < kNumChunks; ++j) { + __m256i sum0 = _mm256_loadA_si256( + &reinterpret_cast(accumulation[perspectives[p]][0])[j * 2 + 0]); + __m256i sum1 = _mm256_loadA_si256( + &reinterpret_cast(accumulation[perspectives[p]][0])[j * 2 + 1]); + _mm256_storeA_si256(&out[j], _mm256_permute4x64_epi64(_mm256_max_epi8( + _mm256_packs_epi16(sum0, sum1), kZero), kControl)); + } + + #elif defined(USE_SSE2) + auto out = reinterpret_cast<__m128i*>(&output[offset]); + for (IndexType j = 0; j < kNumChunks; ++j) { + __m128i sum0 = _mm_load_si128(&reinterpret_cast( + accumulation[perspectives[p]][0])[j * 2 + 0]); + __m128i sum1 = _mm_load_si128(&reinterpret_cast( + accumulation[perspectives[p]][0])[j * 2 + 1]); + const __m128i packedbytes = _mm_packs_epi16(sum0, sum1); + + _mm_store_si128(&out[j], + + #ifdef USE_SSE41 + _mm_max_epi8(packedbytes, kZero) + #else + _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) + #endif + + ); + } + + #elif defined(USE_MMX) + auto out = reinterpret_cast<__m64*>(&output[offset]); + for (IndexType j = 0; j < kNumChunks; ++j) { + __m64 sum0 = *(&reinterpret_cast( + accumulation[perspectives[p]][0])[j * 2 + 0]); + __m64 sum1 = *(&reinterpret_cast( + accumulation[perspectives[p]][0])[j * 2 + 1]); + const __m64 packedbytes = _mm_packs_pi16(sum0, sum1); + out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); + } + + #elif defined(USE_NEON) + const auto out = reinterpret_cast(&output[offset]); + for (IndexType j = 0; j < kNumChunks; ++j) { + int16x8_t sum = reinterpret_cast( + accumulation[perspectives[p]][0])[j]; + out[j] = vmax_s8(vqmovn_s16(sum), kZero); + } + + #else + for (IndexType j = 0; j < kHalfDimensions; ++j) { + BiasType sum = accumulation[static_cast(perspectives[p])][0][j]; + output[offset + j] = static_cast( + std::max(0, std::min(127, sum))); + } + #endif + + } + #if defined(USE_MMX) + _mm_empty(); + #endif + } + + private: + // Calculate cumulative value without using difference calculation + void RefreshAccumulator(const Position& pos) const { + + auto& accumulator = pos.state()->accumulator; + IndexType i = 0; + Features::IndexList active_indices[2]; + RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i], + active_indices); + for (Color perspective : { WHITE, BLACK }) { + #ifdef TILING + for (unsigned j = 0; j < kHalfDimensions / kTileHeight; ++j) { + auto biasesTile = reinterpret_cast( + &biases_[j * kTileHeight]); + auto accTile = reinterpret_cast( + &accumulator.accumulation[perspective][i][j * kTileHeight]); + vec_t acc[kNumRegs]; + + for (unsigned k = 0; k < kNumRegs; ++k) + acc[k] = biasesTile[k]; + + for (const auto index : active_indices[perspective]) { + const IndexType offset = kHalfDimensions * index + j * kTileHeight; + auto column = reinterpret_cast(&weights_[offset]); + + for (unsigned k = 0; k < kNumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + for (unsigned k = 0; k < kNumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + #else + std::memcpy(accumulator.accumulation[perspective][i], biases_, + kHalfDimensions * sizeof(BiasType)); + + for (const auto index : active_indices[perspective]) { + const IndexType offset = kHalfDimensions * index; + + for (IndexType j = 0; j < kHalfDimensions; ++j) + accumulator.accumulation[perspective][i][j] += weights_[offset + j]; + } + #endif + } + + #if defined(USE_MMX) + _mm_empty(); + #endif + + accumulator.computed_accumulation = true; + } + + // Calculate cumulative value using difference calculation + void UpdateAccumulator(const Position& pos) const { + + const auto prev_accumulator = pos.state()->previous->accumulator; + auto& accumulator = pos.state()->accumulator; + IndexType i = 0; + Features::IndexList removed_indices[2], added_indices[2]; + bool reset[2]; + RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i], + removed_indices, added_indices, reset); + + #ifdef TILING + for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) { + for (Color perspective : { WHITE, BLACK }) { + auto accTile = reinterpret_cast( + &accumulator.accumulation[perspective][i][j * kTileHeight]); + vec_t acc[kNumRegs]; + + if (reset[perspective]) { + auto biasesTile = reinterpret_cast( + &biases_[j * kTileHeight]); + for (unsigned k = 0; k < kNumRegs; ++k) + acc[k] = biasesTile[k]; + } else { + auto prevAccTile = reinterpret_cast( + &prev_accumulator.accumulation[perspective][i][j * kTileHeight]); + for (IndexType k = 0; k < kNumRegs; ++k) + acc[k] = vec_load(&prevAccTile[k]); + + // Difference calculation for the deactivated features + for (const auto index : removed_indices[perspective]) { + const IndexType offset = kHalfDimensions * index + j * kTileHeight; + auto column = reinterpret_cast(&weights_[offset]); + + for (IndexType k = 0; k < kNumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + } + { // Difference calculation for the activated features + for (const auto index : added_indices[perspective]) { + const IndexType offset = kHalfDimensions * index + j * kTileHeight; + auto column = reinterpret_cast(&weights_[offset]); + + for (IndexType k = 0; k < kNumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + } + + for (IndexType k = 0; k < kNumRegs; ++k) + vec_store(&accTile[k], acc[k]); + } + } + #if defined(USE_MMX) + _mm_empty(); + #endif + + #else + for (Color perspective : { WHITE, BLACK }) { + + if (reset[perspective]) { + std::memcpy(accumulator.accumulation[perspective][i], biases_, + kHalfDimensions * sizeof(BiasType)); + } else { + std::memcpy(accumulator.accumulation[perspective][i], + prev_accumulator.accumulation[perspective][i], + kHalfDimensions * sizeof(BiasType)); + // Difference calculation for the deactivated features + for (const auto index : removed_indices[perspective]) { + const IndexType offset = kHalfDimensions * index; + + for (IndexType j = 0; j < kHalfDimensions; ++j) + accumulator.accumulation[perspective][i][j] -= weights_[offset + j]; + } + } + { // Difference calculation for the activated features + for (const auto index : added_indices[perspective]) { + const IndexType offset = kHalfDimensions * index; + + for (IndexType j = 0; j < kHalfDimensions; ++j) + accumulator.accumulation[perspective][i][j] += weights_[offset + j]; + } + } + } + #endif + + accumulator.computed_accumulation = true; + } + + using BiasType = std::int16_t; + using WeightType = std::int16_t; + + alignas(kCacheLineSize) BiasType biases_[kHalfDimensions]; + alignas(kCacheLineSize) + WeightType weights_[kHalfDimensions * kInputDimensions]; + }; + +} // namespace Eval::NNUE + +#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED diff --git a/engines/stockfish13/pawns.cpp b/engines/stockfish13/pawns.cpp new file mode 100644 index 0000000..af0f661 --- /dev/null +++ b/engines/stockfish13/pawns.cpp @@ -0,0 +1,281 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "bitboard.h" +#include "pawns.h" +#include "position.h" +#include "thread.h" + +namespace { + + #define V Value + #define S(mg, eg) make_score(mg, eg) + + // Pawn penalties + constexpr Score Backward = S( 8, 27); + constexpr Score Doubled = S(11, 55); + constexpr Score Isolated = S( 5, 17); + constexpr Score WeakLever = S( 2, 54); + constexpr Score WeakUnopposed = S(15, 25); + + // Bonus for blocked pawns at 5th or 6th rank + constexpr Score BlockedPawn[2] = { S(-13, -4), S(-4, 3) }; + + constexpr Score BlockedStorm[RANK_NB] = { + S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2) + }; + + // Connected pawn bonus + constexpr int Connected[RANK_NB] = { 0, 7, 8, 11, 24, 45, 85 }; + + // Strength of pawn shelter for our king by [distance from edge][rank]. + // RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king. + constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = { + { V( -6), V( 81), V( 93), V( 58), V( 39), V( 18), V( 25) }, + { V(-43), V( 61), V( 35), V(-49), V(-29), V(-11), V( -63) }, + { V(-10), V( 75), V( 23), V( -2), V( 32), V( 3), V( -45) }, + { V(-39), V(-13), V(-29), V(-52), V(-48), V(-67), V(-166) } + }; + + // Danger of enemy pawns moving toward our king by [distance from edge][rank]. + // RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn + // is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn + // on edge, likely blocked by our king. + constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = { + { V( 85), V(-289), V(-166), V(97), V(50), V( 45), V( 50) }, + { V( 46), V( -25), V( 122), V(45), V(37), V(-10), V( 20) }, + { V( -6), V( 51), V( 168), V(34), V(-2), V(-22), V(-14) }, + { V(-15), V( -11), V( 101), V( 4), V(11), V(-15), V(-29) } + }; + + #undef S + #undef V + + + /// evaluate() calculates a score for the static pawn structure of the given position. + /// We cannot use the location of pieces or king in this function, as the evaluation + /// of the pawn structure will be stored in a small cache for speed reasons, and will + /// be re-used even when the pieces have moved. + + template + Score evaluate(const Position& pos, Pawns::Entry* e) { + + constexpr Color Them = ~Us; + constexpr Direction Up = pawn_push(Us); + + Bitboard neighbours, stoppers, support, phalanx, opposed; + Bitboard lever, leverPush, blocked; + Square s; + bool backward, passed, doubled; + Score score = SCORE_ZERO; + const Square* pl = pos.squares(Us); + + Bitboard ourPawns = pos.pieces( Us, PAWN); + Bitboard theirPawns = pos.pieces(Them, PAWN); + + Bitboard doubleAttackThem = pawn_double_attacks_bb(theirPawns); + + e->passedPawns[Us] = 0; + e->kingSquares[Us] = SQ_NONE; + e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb(ourPawns); + e->blockedCount += popcount(shift(ourPawns) & (theirPawns | doubleAttackThem)); + + // Loop through all pawns of the current color and score each pawn + while ((s = *pl++) != SQ_NONE) + { + assert(pos.piece_on(s) == make_piece(Us, PAWN)); + + Rank r = relative_rank(Us, s); + + // Flag the pawn + opposed = theirPawns & forward_file_bb(Us, s); + blocked = theirPawns & (s + Up); + stoppers = theirPawns & passed_pawn_span(Us, s); + lever = theirPawns & pawn_attacks_bb(Us, s); + leverPush = theirPawns & pawn_attacks_bb(Us, s + Up); + doubled = ourPawns & (s - Up); + neighbours = ourPawns & adjacent_files_bb(s); + phalanx = neighbours & rank_bb(s); + support = neighbours & rank_bb(s - Up); + + // A pawn is backward when it is behind all pawns of the same color on + // the adjacent files and cannot safely advance. + backward = !(neighbours & forward_ranks_bb(Them, s + Up)) + && (leverPush | blocked); + + // Compute additional span if pawn is not backward nor blocked + if (!backward && !blocked) + e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s); + + // A pawn is passed if one of the three following conditions is true: + // (a) there is no stoppers except some levers + // (b) the only stoppers are the leverPush, but we outnumber them + // (c) there is only one front stopper which can be levered. + // (Refined in Evaluation::passed) + passed = !(stoppers ^ lever) + || ( !(stoppers ^ leverPush) + && popcount(phalanx) >= popcount(leverPush)) + || ( stoppers == blocked && r >= RANK_5 + && (shift(support) & ~(theirPawns | doubleAttackThem))); + + passed &= !(forward_file_bb(Us, s) & ourPawns); + + // Passed pawns will be properly scored later in evaluation when we have + // full attack info. + if (passed) + e->passedPawns[Us] |= s; + + // Score this pawn + if (support | phalanx) + { + int v = Connected[r] * (2 + bool(phalanx) - bool(opposed)) + + 21 * popcount(support); + + score += make_score(v, v * (r - 2) / 4); + } + + else if (!neighbours) + { + if ( opposed + && (ourPawns & forward_file_bb(Them, s)) + && !(theirPawns & adjacent_files_bb(s))) + score -= Doubled; + else + score -= Isolated + + WeakUnopposed * !opposed; + } + + else if (backward) + score -= Backward + + WeakUnopposed * !opposed; + + if (!support) + score -= Doubled * doubled + + WeakLever * more_than_one(lever); + + if (blocked && r > RANK_4) + score += BlockedPawn[r-4]; + } + + return score; + } + +} // namespace + +namespace Pawns { + + +/// Pawns::probe() looks up the current position's pawns configuration in +/// the pawns hash table. It returns a pointer to the Entry if the position +/// is found. Otherwise a new Entry is computed and stored there, so we don't +/// have to recompute all when the same pawns configuration occurs again. + +Entry* probe(const Position& pos) { + + Key key = pos.pawn_key(); + Entry* e = pos.this_thread()->pawnsTable[key]; + + if (e->key == key) + return e; + + e->key = key; + e->blockedCount = 0; + e->scores[WHITE] = evaluate(pos, e); + e->scores[BLACK] = evaluate(pos, e); + + return e; +} + + +/// Entry::evaluate_shelter() calculates the shelter bonus and the storm +/// penalty for a king, looking at the king file and the two closest files. + +template +Score Entry::evaluate_shelter(const Position& pos, Square ksq) const { + + constexpr Color Them = ~Us; + + Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq); + Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them]; + Bitboard theirPawns = b & pos.pieces(Them); + + Score bonus = make_score(5, 5); + + File center = std::clamp(file_of(ksq), FILE_B, FILE_G); + for (File f = File(center - 1); f <= File(center + 1); ++f) + { + b = ourPawns & file_bb(f); + int ourRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0; + + b = theirPawns & file_bb(f); + int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0; + + int d = edge_distance(f); + bonus += make_score(ShelterStrength[d][ourRank], 0); + + if (ourRank && (ourRank == theirRank - 1)) + bonus -= BlockedStorm[theirRank]; + else + bonus -= make_score(UnblockedStorm[d][theirRank], 0); + } + + return bonus; +} + + +/// Entry::do_king_safety() calculates a bonus for king safety. It is called only +/// when king square changes, which is about 20% of total king_safety() calls. + +template +Score Entry::do_king_safety(const Position& pos) { + + Square ksq = pos.square(Us); + kingSquares[Us] = ksq; + castlingRights[Us] = pos.castling_rights(Us); + auto compare = [](Score a, Score b) { return mg_value(a) < mg_value(b); }; + + Score shelter = evaluate_shelter(pos, ksq); + + // If we can castle use the bonus after castling if it is bigger + + if (pos.can_castle(Us & KING_SIDE)) + shelter = std::max(shelter, evaluate_shelter(pos, relative_square(Us, SQ_G1)), compare); + + if (pos.can_castle(Us & QUEEN_SIDE)) + shelter = std::max(shelter, evaluate_shelter(pos, relative_square(Us, SQ_C1)), compare); + + // In endgame we like to bring our king near our closest pawn + Bitboard pawns = pos.pieces(Us, PAWN); + int minPawnDist = 6; + + if (pawns & attacks_bb(ksq)) + minPawnDist = 1; + else while (pawns) + minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns))); + + return shelter - make_score(0, 16 * minPawnDist); +} + +// Explicit template instantiation +template Score Entry::do_king_safety(const Position& pos); +template Score Entry::do_king_safety(const Position& pos); + +} // namespace Pawns diff --git a/engines/stockfish13/pawns.h b/engines/stockfish13/pawns.h new file mode 100644 index 0000000..5499826 --- /dev/null +++ b/engines/stockfish13/pawns.h @@ -0,0 +1,70 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PAWNS_H_INCLUDED +#define PAWNS_H_INCLUDED + +#include "misc.h" +#include "position.h" +#include "types.h" + +namespace Pawns { + +/// Pawns::Entry contains various information about a pawn structure. A lookup +/// to the pawn hash table (performed by calling the probe function) returns a +/// pointer to an Entry object. + +struct Entry { + + Score pawn_score(Color c) const { return scores[c]; } + Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } + Bitboard passed_pawns(Color c) const { return passedPawns[c]; } + Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; } + int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); } + int blocked_count() const { return blockedCount; } + + template + Score king_safety(const Position& pos) { + return kingSquares[Us] == pos.square(Us) && castlingRights[Us] == pos.castling_rights(Us) + ? kingSafety[Us] : (kingSafety[Us] = do_king_safety(pos)); + } + + template + Score do_king_safety(const Position& pos); + + template + Score evaluate_shelter(const Position& pos, Square ksq) const; + + Key key; + Score scores[COLOR_NB]; + Bitboard passedPawns[COLOR_NB]; + Bitboard pawnAttacks[COLOR_NB]; + Bitboard pawnAttacksSpan[COLOR_NB]; + Square kingSquares[COLOR_NB]; + Score kingSafety[COLOR_NB]; + int castlingRights[COLOR_NB]; + int blockedCount; +}; + +typedef HashTable Table; + +Entry* probe(const Position& pos); + +} // namespace Pawns + +#endif // #ifndef PAWNS_H_INCLUDED diff --git a/engines/stockfish13/position.cpp b/engines/stockfish13/position.cpp new file mode 100644 index 0000000..e6a760d --- /dev/null +++ b/engines/stockfish13/position.cpp @@ -0,0 +1,1346 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include // For offsetof() +#include // For std::memset, std::memcmp +#include +#include + +#include "bitboard.h" +#include "misc.h" +#include "movegen.h" +#include "position.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "syzygy/tbprobe.h" + +using std::string; + +namespace Zobrist { + + Key psq[PIECE_NB][SQUARE_NB]; + Key enpassant[FILE_NB]; + Key castling[CASTLING_RIGHT_NB]; + Key side, noPawns; +} + +namespace { + +const string PieceToChar(" PNBRQK pnbrqk"); + +constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; +} // namespace + + +/// operator<<(Position) returns an ASCII representation of the position + +std::ostream& operator<<(std::ostream& os, const Position& pos) { + + os << "\n +---+---+---+---+---+---+---+---+\n"; + + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; + + os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; + } + + os << " a b c d e f g h\n" + << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase + << std::setfill('0') << std::setw(16) << pos.key() + << std::setfill(' ') << std::dec << "\nCheckers: "; + + for (Bitboard b = pos.checkers(); b; ) + os << UCI::square(pop_lsb(&b)) << " "; + + if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) + && !pos.can_castle(ANY_CASTLING)) + { + StateInfo st; + Position p; + p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); + Tablebases::ProbeState s1, s2; + Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); + int dtz = Tablebases::probe_dtz(p, &s2); + os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" + << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; + } + + return os; +} + + +// Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition" +// situations. Description of the algorithm in the following paper: +// https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf + +// First and second hash functions for indexing the cuckoo tables +inline int H1(Key h) { return h & 0x1fff; } +inline int H2(Key h) { return (h >> 16) & 0x1fff; } + +// Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves +Key cuckoo[8192]; +Move cuckooMove[8192]; + + +/// Position::init() initializes at startup the various arrays used to compute hash keys + +void Position::init() { + + PRNG rng(1070372); + + for (Piece pc : Pieces) + for (Square s = SQ_A1; s <= SQ_H8; ++s) + Zobrist::psq[pc][s] = rng.rand(); + + for (File f = FILE_A; f <= FILE_H; ++f) + Zobrist::enpassant[f] = rng.rand(); + + for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) + Zobrist::castling[cr] = rng.rand(); + + Zobrist::side = rng.rand(); + Zobrist::noPawns = rng.rand(); + + // Prepare the cuckoo tables + std::memset(cuckoo, 0, sizeof(cuckoo)); + std::memset(cuckooMove, 0, sizeof(cuckooMove)); + int count = 0; + for (Piece pc : Pieces) + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) + if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) + { + Move move = make_move(s1, s2); + Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; + int i = H1(key); + while (true) + { + std::swap(cuckoo[i], key); + std::swap(cuckooMove[i], move); + if (move == MOVE_NONE) // Arrived at empty slot? + break; + i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot + } + count++; + } + assert(count == 3668); +} + + +/// Position::set() initializes the position object with the given FEN string. +/// This function is not very robust - make sure that input FENs are correct, +/// this is assumed to be the responsibility of the GUI. + +Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { +/* + A FEN string defines a particular position using only the ASCII character set. + + A FEN string contains six fields separated by a space. The fields are: + + 1) Piece placement (from white's perspective). Each rank is described, starting + with rank 8 and ending with rank 1. Within each rank, the contents of each + square are described from file A through file H. Following the Standard + Algebraic Notation (SAN), each piece is identified by a single letter taken + from the standard English names. White pieces are designated using upper-case + letters ("PNBRQK") whilst Black uses lowercase ("pnbrqk"). Blank squares are + noted using digits 1 through 8 (the number of blank squares), and "/" + separates ranks. + + 2) Active color. "w" means white moves next, "b" means black. + + 3) Castling availability. If neither side can castle, this is "-". Otherwise, + this has one or more letters: "K" (White can castle kingside), "Q" (White + can castle queenside), "k" (Black can castle kingside), and/or "q" (Black + can castle queenside). + + 4) En passant target square (in algebraic notation). If there's no en passant + target square, this is "-". If a pawn has just made a 2-square move, this + is the position "behind" the pawn. Following X-FEN standard, this is recorded only + if there is a pawn in position to make an en passant capture, and if there really + is a pawn that might have advanced two squares. + + 5) Halfmove clock. This is the number of halfmoves since the last pawn advance + or capture. This is used to determine if a draw can be claimed under the + fifty-move rule. + + 6) Fullmove number. The number of the full move. It starts at 1, and is + incremented after Black's move. +*/ + + unsigned char col, row, token; + size_t idx; + Square sq = SQ_A8; + std::istringstream ss(fenStr); + + std::memset(this, 0, sizeof(Position)); + std::memset(si, 0, sizeof(StateInfo)); + std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE); + st = si; + + ss >> std::noskipws; + + // 1. Piece placement + while ((ss >> token) && !isspace(token)) + { + if (isdigit(token)) + sq += (token - '0') * EAST; // Advance the given number of files + + else if (token == '/') + sq += 2 * SOUTH; + + else if ((idx = PieceToChar.find(token)) != string::npos) { + put_piece(Piece(idx), sq); + ++sq; + } + } + + // 2. Active color + ss >> token; + sideToMove = (token == 'w' ? WHITE : BLACK); + ss >> token; + + // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, + // Shredder-FEN that uses the letters of the columns on which the rooks began + // the game instead of KQkq and also X-FEN standard that, in case of Chess960, + // if an inner rook is associated with the castling right, the castling tag is + // replaced by the file letter of the involved rook, as for the Shredder-FEN. + while ((ss >> token) && !isspace(token)) + { + Square rsq; + Color c = islower(token) ? BLACK : WHITE; + Piece rook = make_piece(c, ROOK); + + token = char(toupper(token)); + + if (token == 'K') + for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {} + + else if (token == 'Q') + for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {} + + else if (token >= 'A' && token <= 'H') + rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); + + else + continue; + + set_castling_right(c, rsq); + } + + // 4. En passant square. + // Ignore if square is invalid or not on side to move relative rank 6. + bool enpassant = false; + + if ( ((ss >> col) && (col >= 'a' && col <= 'h')) + && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) + { + st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + + // En passant square will be considered only if + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn in front of epSquare + // c) there is no piece on epSquare or behind epSquare + enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) + && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + } + + if (!enpassant) + st->epSquare = SQ_NONE; + + // 5-6. Halfmove clock and fullmove number + ss >> std::skipws >> st->rule50 >> gamePly; + + // Convert from fullmove starting from 1 to gamePly starting from 0, + // handle also common incorrect FEN with fullmove = 0. + gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); + + chess960 = isChess960; + thisThread = th; + set_state(st); + + assert(pos_is_ok()); + + return *this; +} + + +/// Position::set_castling_right() is a helper function used to set castling +/// rights given the corresponding color and the rook starting square. + +void Position::set_castling_right(Color c, Square rfrom) { + + Square kfrom = square(c); + CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE); + + st->castlingRights |= cr; + castlingRightsMask[kfrom] |= cr; + castlingRightsMask[rfrom] |= cr; + castlingRookSquare[cr] = rfrom; + + Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); + Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); + + castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto) + & ~(kfrom | rfrom); +} + + +/// Position::set_check_info() sets king attacks to detect if a move gives check + +void Position::set_check_info(StateInfo* si) const { + + si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), si->pinners[BLACK]); + si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), si->pinners[WHITE]); + + Square ksq = square(~sideToMove); + + si->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + si->checkSquares[KNIGHT] = attacks_bb(ksq); + si->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); + si->checkSquares[ROOK] = attacks_bb(ksq, pieces()); + si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK]; + si->checkSquares[KING] = 0; +} + + +/// Position::set_state() computes the hash keys of the position, and other +/// data that once computed is updated incrementally as moves are made. +/// The function is only used when a new position is set up, and to verify +/// the correctness of the StateInfo data when running in debug mode. + +void Position::set_state(StateInfo* si) const { + + si->key = si->materialKey = 0; + si->pawnKey = Zobrist::noPawns; + si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; + si->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); + + set_check_info(si); + + for (Bitboard b = pieces(); b; ) + { + Square s = pop_lsb(&b); + Piece pc = piece_on(s); + si->key ^= Zobrist::psq[pc][s]; + + if (type_of(pc) == PAWN) + si->pawnKey ^= Zobrist::psq[pc][s]; + + else if (type_of(pc) != KING) + si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; + } + + if (si->epSquare != SQ_NONE) + si->key ^= Zobrist::enpassant[file_of(si->epSquare)]; + + if (sideToMove == BLACK) + si->key ^= Zobrist::side; + + si->key ^= Zobrist::castling[si->castlingRights]; + + for (Piece pc : Pieces) + for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) + si->materialKey ^= Zobrist::psq[pc][cnt]; +} + + +/// Position::set() is an overload to initialize the position object with +/// the given endgame code string like "KBPKN". It is mainly a helper to +/// get the material key out of an endgame code. + +Position& Position::set(const string& code, Color c, StateInfo* si) { + + assert(code[0] == 'K'); + + string sides[] = { code.substr(code.find('K', 1)), // Weak + code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong + + assert(sides[0].length() > 0 && sides[0].length() < 8); + assert(sides[1].length() > 0 && sides[1].length() < 8); + + std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); + + string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; + + return set(fenStr, false, si, nullptr); +} + + +/// Position::fen() returns a FEN representation of the position. In case of +/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. + +const string Position::fen() const { + + int emptyCnt; + std::ostringstream ss; + + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + { + for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f) + ++emptyCnt; + + if (emptyCnt) + ss << emptyCnt; + + if (f <= FILE_H) + ss << PieceToChar[piece_on(make_square(f, r))]; + } + + if (r > RANK_1) + ss << '/'; + } + + ss << (sideToMove == WHITE ? " w " : " b "); + + if (can_castle(WHITE_OO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K'); + + if (can_castle(WHITE_OOO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); + + if (can_castle(BLACK_OO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k'); + + if (can_castle(BLACK_OOO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q'); + + if (!can_castle(ANY_CASTLING)) + ss << '-'; + + ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") + << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; + + return ss.str(); +} + + +/// Position::slider_blockers() returns a bitboard of all the pieces (both colors) +/// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a +/// slider if removing that piece from the board would result in a position where +/// square 's' is attacked. For example, a king-attack blocking piece can be either +/// a pinned or a discovered check piece, according if its color is the opposite +/// or the same of the color of the slider. + +Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const { + + Bitboard blockers = 0; + pinners = 0; + + // Snipers are sliders that attack 's' when a piece and other snipers are removed + Bitboard snipers = ( (attacks_bb< ROOK>(s) & pieces(QUEEN, ROOK)) + | (attacks_bb(s) & pieces(QUEEN, BISHOP))) & sliders; + Bitboard occupancy = pieces() ^ snipers; + + while (snipers) + { + Square sniperSq = pop_lsb(&snipers); + Bitboard b = between_bb(s, sniperSq) & occupancy; + + if (b && !more_than_one(b)) + { + blockers |= b; + if (b & pieces(color_of(piece_on(s)))) + pinners |= sniperSq; + } + } + return blockers; +} + + +/// Position::attackers_to() computes a bitboard of all pieces which attack a +/// given square. Slider attacks use the occupied bitboard to indicate occupancy. + +Bitboard Position::attackers_to(Square s, Bitboard occupied) const { + + return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) + | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) + | (attacks_bb(s) & pieces(KNIGHT)) + | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN)) + | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(s) & pieces(KING)); +} + + +/// Position::legal() tests whether a pseudo-legal move is legal + +bool Position::legal(Move m) const { + + assert(is_ok(m)); + + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + + assert(color_of(moved_piece(m)) == us); + assert(piece_on(square(us)) == make_piece(us, KING)); + + // En passant captures are a tricky special case. Because they are rather + // uncommon, we do it simply by testing whether the king is attacked after + // the move is made. + if (type_of(m) == ENPASSANT) + { + Square ksq = square(us); + Square capsq = to - pawn_push(us); + Bitboard occupied = (pieces() ^ from ^ capsq) | to; + + assert(to == ep_square()); + assert(moved_piece(m) == make_piece(us, PAWN)); + assert(piece_on(capsq) == make_piece(~us, PAWN)); + assert(piece_on(to) == NO_PIECE); + + return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK)) + && !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, BISHOP)); + } + + // Castling moves generation does not check if the castling path is clear of + // enemy attacks, it is delayed at a later time: now! + if (type_of(m) == CASTLING) + { + // After castling, the rook and king final positions are the same in + // Chess960 as they would be in standard chess. + to = relative_square(us, to > from ? SQ_G1 : SQ_C1); + Direction step = to > from ? WEST : EAST; + + for (Square s = to; s != from; s += step) + if (attackers_to(s) & pieces(~us)) + return false; + + // In case of Chess960, verify that when moving the castling rook we do + // not discover some hidden checker. + // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. + return !chess960 + || !(attacks_bb(to, pieces() ^ to_sq(m)) & pieces(~us, ROOK, QUEEN)); + } + + // If the moving piece is a king, check whether the destination square is + // attacked by the opponent. + if (type_of(piece_on(from)) == KING) + return !(attackers_to(to) & pieces(~us)); + + // A non-king move is legal if and only if it is not pinned or it + // is moving along the ray towards or away from the king. + return !(blockers_for_king(us) & from) + || aligned(from, to, square(us)); +} + + +/// Position::pseudo_legal() takes a random move and tests whether the move is +/// pseudo legal. It is used to validate moves from TT that can be corrupted +/// due to SMP concurrent access or hash position key aliasing. + +bool Position::pseudo_legal(const Move m) const { + + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = moved_piece(m); + + // Use a slower but simpler function for uncommon cases + if (type_of(m) != NORMAL) + return MoveList(*this).contains(m); + + // Is not a promotion, so promotion piece must be empty + if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE) + return false; + + // If the 'from' square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if (pc == NO_PIECE || color_of(pc) != us) + return false; + + // The destination square cannot be occupied by a friendly piece + if (pieces(us) & to) + return false; + + // Handle the special case of a pawn move + if (type_of(pc) == PAWN) + { + // We have already handled promotion moves, so destination + // cannot be on the 8th/1st rank. + if ((Rank8BB | Rank1BB) & to) + return false; + + if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture + && !((from + pawn_push(us) == to) && empty(to)) // Not a single push + && !( (from + 2 * pawn_push(us) == to) // Not a double push + && (relative_rank(us, from) == RANK_2) + && empty(to) + && empty(to - pawn_push(us)))) + return false; + } + else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) + return false; + + // Evasions generator already takes care to avoid some kind of illegal moves + // and legal() relies on this. We therefore have to take care that the same + // kind of moves are filtered out here. + if (checkers()) + { + if (type_of(pc) != KING) + { + // Double check? In this case a king move is required + if (more_than_one(checkers())) + return false; + + // Our move must be a blocking evasion or a capture of the checking piece + if (!((between_bb(lsb(checkers()), square(us)) | checkers()) & to)) + return false; + } + // In case of king moves under check we have to remove king so as to catch + // invalid moves like b1a1 when opposite queen is on c1. + else if (attackers_to(to, pieces() ^ from) & pieces(~us)) + return false; + } + + return true; +} + + +/// Position::gives_check() tests whether a pseudo-legal move gives a check + +bool Position::gives_check(Move m) const { + + assert(is_ok(m)); + assert(color_of(moved_piece(m)) == sideToMove); + + Square from = from_sq(m); + Square to = to_sq(m); + + // Is there a direct check? + if (check_squares(type_of(piece_on(from))) & to) + return true; + + // Is there a discovered check? + if ( (blockers_for_king(~sideToMove) & from) + && !aligned(from, to, square(~sideToMove))) + return true; + + switch (type_of(m)) + { + case NORMAL: + return false; + + case PROMOTION: + return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); + + // En passant capture with check? We have already handled the case + // of direct checks and ordinary discovered check, so the only case we + // need to handle is the unusual case of a discovered check through + // the captured pawn. + case ENPASSANT: + { + Square capsq = make_square(file_of(to), rank_of(from)); + Bitboard b = (pieces() ^ from ^ capsq) | to; + + return (attacks_bb< ROOK>(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) + | (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP)); + } + case CASTLING: + { + Square kfrom = from; + Square rfrom = to; // Castling is encoded as 'king captures the rook' + Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1); + Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1); + + return (attacks_bb(rto) & square(~sideToMove)) + && (attacks_bb(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square(~sideToMove)); + } + default: + assert(false); + return false; + } +} + + +/// Position::do_move() makes a move, and saves all information necessary +/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal +/// moves should be filtered out before this function is called. + +void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { + + assert(is_ok(m)); + assert(&newSt != st); + + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); + Key k = st->key ^ Zobrist::side; + + // Copy some fields of the old state to our new StateInfo object except the + // ones which are going to be recalculated from scratch anyway and then switch + // our state pointer to point to the new (ready to be updated) state. + std::memcpy(&newSt, st, offsetof(StateInfo, key)); + newSt.previous = st; + st = &newSt; + + // Increment ply counters. In particular, rule50 will be reset to zero later on + // in case of a capture or a pawn move. + ++gamePly; + ++st->rule50; + ++st->pliesFromNull; + + // Used by NNUE + st->accumulator.computed_accumulation = false; + auto& dp = st->dirtyPiece; + dp.dirty_num = 1; + + Color us = sideToMove; + Color them = ~us; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(from); + Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to); + + assert(color_of(pc) == us); + assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); + assert(type_of(captured) != KING); + + if (type_of(m) == CASTLING) + { + assert(pc == make_piece(us, KING)); + assert(captured == make_piece(us, ROOK)); + + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + + k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; + captured = NO_PIECE; + } + + if (captured) + { + Square capsq = to; + + // If the captured piece is a pawn, update pawn hash key, otherwise + // update non-pawn material. + if (type_of(captured) == PAWN) + { + if (type_of(m) == ENPASSANT) + { + capsq -= pawn_push(us); + + assert(pc == make_piece(us, PAWN)); + assert(to == st->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(to) == NO_PIECE); + assert(piece_on(capsq) == make_piece(them, PAWN)); + } + + st->pawnKey ^= Zobrist::psq[captured][capsq]; + } + else + st->nonPawnMaterial[them] -= PieceValue[MG][captured]; + + if (Eval::useNNUE) + { + dp.dirty_num = 2; // 1 piece moved, 1 piece captured + dp.piece[1] = captured; + dp.from[1] = capsq; + dp.to[1] = SQ_NONE; + } + + // Update board and piece lists + remove_piece(capsq); + + if (type_of(m) == ENPASSANT) + board[capsq] = NO_PIECE; + + // Update material hash key and prefetch access to materialTable + k ^= Zobrist::psq[captured][capsq]; + st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; + prefetch(thisThread->materialTable[st->materialKey]); + + // Reset rule 50 counter + st->rule50 = 0; + } + + // Update hash key + k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + // Reset en passant square + if (st->epSquare != SQ_NONE) + { + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } + + // Update castling rights if needed + if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) + { + k ^= Zobrist::castling[st->castlingRights]; + st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); + k ^= Zobrist::castling[st->castlingRights]; + } + + // Move the piece. The tricky Chess960 castling is handled earlier + if (type_of(m) != CASTLING) + { + if (Eval::useNNUE) + { + dp.piece[0] = pc; + dp.from[0] = from; + dp.to[0] = to; + } + + move_piece(from, to); + } + + // If the moving piece is a pawn do some special extra work + if (type_of(pc) == PAWN) + { + // Set en-passant square if the moved pawn can be captured + if ( (int(to) ^ int(from)) == 16 + && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) + { + st->epSquare = to - pawn_push(us); + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + } + + else if (type_of(m) == PROMOTION) + { + Piece promotion = make_piece(us, promotion_type(m)); + + assert(relative_rank(us, to) == RANK_8); + assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); + + remove_piece(to); + put_piece(promotion, to); + + if (Eval::useNNUE) + { + // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE + dp.to[0] = SQ_NONE; + dp.piece[dp.dirty_num] = promotion; + dp.from[dp.dirty_num] = SQ_NONE; + dp.to[dp.dirty_num] = to; + dp.dirty_num++; + } + + // Update hash keys + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; + st->pawnKey ^= Zobrist::psq[pc][to]; + st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1] + ^ Zobrist::psq[pc][pieceCount[pc]]; + + // Update material + st->nonPawnMaterial[us] += PieceValue[MG][promotion]; + } + + // Update pawn hash key + st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + // Reset rule 50 draw counter + st->rule50 = 0; + } + + // Set capture piece + st->capturedPiece = captured; + + // Update the key with the final value + st->key = k; + + // Calculate checkers bitboard (if move gives check) + st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; + + sideToMove = ~sideToMove; + + // Update king attacks used for fast check detection + set_check_info(st); + + // Calculate the repetition info. It is the ply distance from the previous + // occurrence of the same position, negative in the 3-fold case, or zero + // if the position was not repeated. + st->repetition = 0; + int end = std::min(st->rule50, st->pliesFromNull); + if (end >= 4) + { + StateInfo* stp = st->previous->previous; + for (int i = 4; i <= end; i += 2) + { + stp = stp->previous->previous; + if (stp->key == st->key) + { + st->repetition = stp->repetition ? -i : i; + break; + } + } + } + + assert(pos_is_ok()); +} + + +/// Position::undo_move() unmakes a move. When it returns, the position should +/// be restored to exactly the same state as before the move was made. + +void Position::undo_move(Move m) { + + assert(is_ok(m)); + + sideToMove = ~sideToMove; + + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(to); + + assert(empty(from) || type_of(m) == CASTLING); + assert(type_of(st->capturedPiece) != KING); + + if (type_of(m) == PROMOTION) + { + assert(relative_rank(us, to) == RANK_8); + assert(type_of(pc) == promotion_type(m)); + assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); + + remove_piece(to); + pc = make_piece(us, PAWN); + put_piece(pc, to); + } + + if (type_of(m) == CASTLING) + { + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + } + else + { + move_piece(to, from); // Put the piece back at the source square + + if (st->capturedPiece) + { + Square capsq = to; + + if (type_of(m) == ENPASSANT) + { + capsq -= pawn_push(us); + + assert(type_of(pc) == PAWN); + assert(to == st->previous->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(capsq) == NO_PIECE); + assert(st->capturedPiece == make_piece(~us, PAWN)); + } + + put_piece(st->capturedPiece, capsq); // Restore the captured piece + } + } + + // Finally point our state pointer back to the previous state + st = st->previous; + --gamePly; + + assert(pos_is_ok()); +} + + +/// Position::do_castling() is a helper used to do/undo a castling move. This +/// is a bit tricky in Chess960 where from/to squares can overlap. +template +void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { + + bool kingSide = to > from; + rfrom = to; // Castling is encoded as "king captures friendly rook" + rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); + to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + + if (Do && Eval::useNNUE) + { + auto& dp = st->dirtyPiece; + dp.piece[0] = make_piece(us, KING); + dp.from[0] = from; + dp.to[0] = to; + dp.piece[1] = make_piece(us, ROOK); + dp.from[1] = rfrom; + dp.to[1] = rto; + dp.dirty_num = 2; + } + + // Remove both pieces first since squares could overlap in Chess960 + remove_piece(Do ? from : to); + remove_piece(Do ? rfrom : rto); + board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us + put_piece(make_piece(us, KING), Do ? to : from); + put_piece(make_piece(us, ROOK), Do ? rto : rfrom); +} + + +/// Position::do(undo)_null_move() is used to do(undo) a "null move": it flips +/// the side to move without executing any move on the board. + +void Position::do_null_move(StateInfo& newSt) { + + assert(!checkers()); + assert(&newSt != st); + + if (Eval::useNNUE) + { + std::memcpy(&newSt, st, sizeof(StateInfo)); + } + else + std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); + + newSt.previous = st; + st = &newSt; + + if (st->epSquare != SQ_NONE) + { + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } + + st->key ^= Zobrist::side; + prefetch(TT.first_entry(st->key)); + + ++st->rule50; + st->pliesFromNull = 0; + + sideToMove = ~sideToMove; + + set_check_info(st); + + st->repetition = 0; + + assert(pos_is_ok()); +} + +void Position::undo_null_move() { + + assert(!checkers()); + + st = st->previous; + sideToMove = ~sideToMove; +} + + +/// Position::key_after() computes the new hash key after the given move. Needed +/// for speculative prefetch. It doesn't recognize special moves like castling, +/// en-passant and promotions. + +Key Position::key_after(Move m) const { + + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(from); + Piece captured = piece_on(to); + Key k = st->key ^ Zobrist::side; + + if (captured) + k ^= Zobrist::psq[captured][to]; + + return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; +} + + +/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the +/// SEE value of move is greater or equal to the given threshold. We'll use an +/// algorithm similar to alpha-beta pruning with a null window. + +bool Position::see_ge(Move m, Value threshold) const { + + assert(is_ok(m)); + + // Only deal with normal moves, assume others pass a simple see + if (type_of(m) != NORMAL) + return VALUE_ZERO >= threshold; + + Square from = from_sq(m), to = to_sq(m); + + int swap = PieceValue[MG][piece_on(to)] - threshold; + if (swap < 0) + return false; + + swap = PieceValue[MG][piece_on(from)] - swap; + if (swap <= 0) + return true; + + Bitboard occupied = pieces() ^ from ^ to; + Color stm = color_of(piece_on(from)); + Bitboard attackers = attackers_to(to, occupied); + Bitboard stmAttackers, bb; + int res = 1; + + while (true) + { + stm = ~stm; + attackers &= occupied; + + // If stm has no more attackers then give up: stm loses + if (!(stmAttackers = attackers & pieces(stm))) + break; + + // Don't allow pinned pieces to attack (except the king) as long as + // there are pinners on their original square. + if (pinners(~stm) & occupied) + stmAttackers &= ~blockers_for_king(stm); + + if (!stmAttackers) + break; + + res ^= 1; + + // Locate and remove the next least valuable attacker, and add to + // the bitboard 'attackers' any X-ray attackers behind it. + if ((bb = stmAttackers & pieces(PAWN))) + { + if ((swap = PawnValueMg - swap) < res) + break; + + occupied ^= lsb(bb); + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } + + else if ((bb = stmAttackers & pieces(KNIGHT))) + { + if ((swap = KnightValueMg - swap) < res) + break; + + occupied ^= lsb(bb); + } + + else if ((bb = stmAttackers & pieces(BISHOP))) + { + if ((swap = BishopValueMg - swap) < res) + break; + + occupied ^= lsb(bb); + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } + + else if ((bb = stmAttackers & pieces(ROOK))) + { + if ((swap = RookValueMg - swap) < res) + break; + + occupied ^= lsb(bb); + attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); + } + + else if ((bb = stmAttackers & pieces(QUEEN))) + { + if ((swap = QueenValueMg - swap) < res) + break; + + occupied ^= lsb(bb); + attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); + } + + else // KING + // If we "capture" with the king but opponent still has attackers, + // reverse the result. + return (attackers & ~pieces(stm)) ? res ^ 1 : res; + } + + return bool(res); +} + + +/// Position::is_draw() tests whether the position is drawn by 50-move rule +/// or by repetition. It does not detect stalemates. + +bool Position::is_draw(int ply) const { + + if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) + return true; + + // Return a draw score if a position repeats once earlier but strictly + // after the root, or repeats twice before or at the root. + return st->repetition && st->repetition < ply; +} + + +// Position::has_repeated() tests whether there has been at least one repetition +// of positions since the last capture or pawn move. + +bool Position::has_repeated() const { + + StateInfo* stc = st; + int end = std::min(st->rule50, st->pliesFromNull); + while (end-- >= 4) + { + if (stc->repetition) + return true; + + stc = stc->previous; + } + return false; +} + + +/// Position::has_game_cycle() tests if the position has a move which draws by repetition, +/// or an earlier position has a move that directly reaches the current position. + +bool Position::has_game_cycle(int ply) const { + + int j; + + int end = std::min(st->rule50, st->pliesFromNull); + + if (end < 3) + return false; + + Key originalKey = st->key; + StateInfo* stp = st->previous; + + for (int i = 3; i <= end; i += 2) + { + stp = stp->previous->previous; + + Key moveKey = originalKey ^ stp->key; + if ( (j = H1(moveKey), cuckoo[j] == moveKey) + || (j = H2(moveKey), cuckoo[j] == moveKey)) + { + Move move = cuckooMove[j]; + Square s1 = from_sq(move); + Square s2 = to_sq(move); + + if (!(between_bb(s1, s2) & pieces())) + { + if (ply > i) + return true; + + // For nodes before or at the root, check that the move is a + // repetition rather than a move to the current position. + // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in + // the same location, so we have to select which square to check. + if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) + continue; + + // For repetitions before or at the root, require one more + if (stp->repetition) + return true; + } + } + } + return false; +} + + +/// Position::flip() flips position with the white and black sides reversed. This +/// is only useful for debugging e.g. for finding evaluation symmetry bugs. + +void Position::flip() { + + string f, token; + std::stringstream ss(fen()); + + for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement + { + std::getline(ss, token, r > RANK_1 ? '/' : ' '); + f.insert(0, token + (f.empty() ? " " : "/")); + } + + ss >> token; // Active color + f += (token == "w" ? "B " : "W "); // Will be lowercased later + + ss >> token; // Castling availability + f += token + " "; + + std::transform(f.begin(), f.end(), f.begin(), + [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); + + ss >> token; // En passant square + f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); + + std::getline(ss, token); // Half and full moves + f += token; + + set(f, is_chess960(), st, this_thread()); + + assert(pos_is_ok()); +} + + +/// Position::pos_is_ok() performs some consistency checks for the +/// position object and raises an asserts if something wrong is detected. +/// This is meant to be helpful when debugging. + +bool Position::pos_is_ok() const { + + constexpr bool Fast = true; // Quick (default) or full check? + + if ( (sideToMove != WHITE && sideToMove != BLACK) + || piece_on(square(WHITE)) != W_KING + || piece_on(square(BLACK)) != B_KING + || ( ep_square() != SQ_NONE + && relative_rank(sideToMove, ep_square()) != RANK_6)) + assert(0 && "pos_is_ok: Default"); + + if (Fast) + return true; + + if ( pieceCount[W_KING] != 1 + || pieceCount[B_KING] != 1 + || attackers_to(square(~sideToMove)) & pieces(sideToMove)) + assert(0 && "pos_is_ok: Kings"); + + if ( (pieces(PAWN) & (Rank1BB | Rank8BB)) + || pieceCount[W_PAWN] > 8 + || pieceCount[B_PAWN] > 8) + assert(0 && "pos_is_ok: Pawns"); + + if ( (pieces(WHITE) & pieces(BLACK)) + || (pieces(WHITE) | pieces(BLACK)) != pieces() + || popcount(pieces(WHITE)) > 16 + || popcount(pieces(BLACK)) > 16) + assert(0 && "pos_is_ok: Bitboards"); + + for (PieceType p1 = PAWN; p1 <= KING; ++p1) + for (PieceType p2 = PAWN; p2 <= KING; ++p2) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + assert(0 && "pos_is_ok: Bitboards"); + + StateInfo si = *st; + set_state(&si); + if (std::memcmp(&si, st, sizeof(StateInfo))) + assert(0 && "pos_is_ok: State"); + + for (Piece pc : Pieces) + { + if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) + || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + assert(0 && "pos_is_ok: Pieces"); + + for (int i = 0; i < pieceCount[pc]; ++i) + if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i) + assert(0 && "pos_is_ok: Index"); + } + + for (Color c : { WHITE, BLACK }) + for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) + { + if (!can_castle(cr)) + continue; + + if ( piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK) + || castlingRightsMask[castlingRookSquare[cr]] != cr + || (castlingRightsMask[square(c)] & cr) != cr) + assert(0 && "pos_is_ok: Castling"); + } + + return true; +} diff --git a/engines/stockfish13/position.h b/engines/stockfish13/position.h new file mode 100644 index 0000000..d6f5c9f --- /dev/null +++ b/engines/stockfish13/position.h @@ -0,0 +1,447 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef POSITION_H_INCLUDED +#define POSITION_H_INCLUDED + +#include +#include +#include // For std::unique_ptr +#include + +#include "bitboard.h" +#include "evaluate.h" +#include "types.h" + +#include "nnue/nnue_accumulator.h" + + +/// StateInfo struct stores information needed to restore a Position object to +/// its previous state when we retract a move. Whenever a move is made on the +/// board (by calling Position::do_move), a StateInfo object must be passed. + +struct StateInfo { + + // Copied when making a move + Key pawnKey; + Key materialKey; + Value nonPawnMaterial[COLOR_NB]; + int castlingRights; + int rule50; + int pliesFromNull; + Square epSquare; + + // Not copied when making a move (will be recomputed anyhow) + Key key; + Bitboard checkersBB; + Piece capturedPiece; + StateInfo* previous; + Bitboard blockersForKing[COLOR_NB]; + Bitboard pinners[COLOR_NB]; + Bitboard checkSquares[PIECE_TYPE_NB]; + int repetition; + + // Used by NNUE + Eval::NNUE::Accumulator accumulator; + DirtyPiece dirtyPiece; +}; + + +/// A list to keep track of the position states along the setup moves (from the +/// start position to the position just before the search starts). Needed by +/// 'draw by repetition' detection. Use a std::deque because pointers to +/// elements are not invalidated upon list resizing. +typedef std::unique_ptr> StateListPtr; + + +/// Position class stores information regarding the board representation as +/// pieces, side to move, hash keys, castling info, etc. Important methods are +/// do_move() and undo_move(), used by the search to update node info when +/// traversing the search tree. +class Thread; + +class Position { +public: + static void init(); + + Position() = default; + Position(const Position&) = delete; + Position& operator=(const Position&) = delete; + + // FEN string input/output + Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); + Position& set(const std::string& code, Color c, StateInfo* si); + const std::string fen() const; + + // Position representation + Bitboard pieces(PieceType pt) const; + Bitboard pieces(PieceType pt1, PieceType pt2) const; + Bitboard pieces(Color c) const; + Bitboard pieces(Color c, PieceType pt) const; + Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const; + Piece piece_on(Square s) const; + Square ep_square() const; + bool empty(Square s) const; + template int count(Color c) const; + template int count() const; + template const Square* squares(Color c) const; + template Square square(Color c) const; + bool is_on_semiopen_file(Color c, Square s) const; + + // Castling + CastlingRights castling_rights(Color c) const; + bool can_castle(CastlingRights cr) const; + bool castling_impeded(CastlingRights cr) const; + Square castling_rook_square(CastlingRights cr) const; + + // Checking + Bitboard checkers() const; + Bitboard blockers_for_king(Color c) const; + Bitboard check_squares(PieceType pt) const; + Bitboard pinners(Color c) const; + bool is_discovery_check_on_king(Color c, Move m) const; + + // Attacks to/from a given square + Bitboard attackers_to(Square s) const; + Bitboard attackers_to(Square s, Bitboard occupied) const; + Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const; + + // Properties of moves + bool legal(Move m) const; + bool pseudo_legal(const Move m) const; + bool capture(Move m) const; + bool capture_or_promotion(Move m) const; + bool gives_check(Move m) const; + bool advanced_pawn_push(Move m) const; + Piece moved_piece(Move m) const; + Piece captured_piece() const; + + // Piece specific + bool pawn_passed(Color c, Square s) const; + bool opposite_bishops() const; + int pawns_on_same_color_squares(Color c, Square s) const; + + // Doing and undoing moves + void do_move(Move m, StateInfo& newSt); + void do_move(Move m, StateInfo& newSt, bool givesCheck); + void undo_move(Move m); + void do_null_move(StateInfo& newSt); + void undo_null_move(); + + // Static Exchange Evaluation + bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + + // Accessing hash keys + Key key() const; + Key key_after(Move m) const; + Key material_key() const; + Key pawn_key() const; + + // Other properties of the position + Color side_to_move() const; + int game_ply() const; + bool is_chess960() const; + Thread* this_thread() const; + bool is_draw(int ply) const; + bool has_game_cycle(int ply) const; + bool has_repeated() const; + int rule50_count() const; + Score psq_score() const; + Value non_pawn_material(Color c) const; + Value non_pawn_material() const; + + // Position consistency check, for debugging + bool pos_is_ok() const; + void flip(); + + // Used by NNUE + StateInfo* state() const; + +private: + // Initialization helpers (used while setting up a position) + void set_castling_right(Color c, Square rfrom); + void set_state(StateInfo* si) const; + void set_check_info(StateInfo* si) const; + + // Other helpers + void put_piece(Piece pc, Square s); + void remove_piece(Square s); + void move_piece(Square from, Square to); + template + void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + + // Data members + Piece board[SQUARE_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; + Bitboard byColorBB[COLOR_NB]; + int pieceCount[PIECE_NB]; + Square pieceList[PIECE_NB][16]; + int index[SQUARE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + int gamePly; + Color sideToMove; + Score psq; + Thread* thisThread; + StateInfo* st; + bool chess960; +}; + +namespace PSQT { + extern Score psq[PIECE_NB][SQUARE_NB]; +} + +extern std::ostream& operator<<(std::ostream& os, const Position& pos); + +inline Color Position::side_to_move() const { + return sideToMove; +} + +inline Piece Position::piece_on(Square s) const { + assert(is_ok(s)); + return board[s]; +} + +inline bool Position::empty(Square s) const { + return piece_on(s) == NO_PIECE; +} + +inline Piece Position::moved_piece(Move m) const { + return piece_on(from_sq(m)); +} + +inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const { + return byTypeBB[pt]; +} + +inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const { + return pieces(pt1) | pieces(pt2); +} + +inline Bitboard Position::pieces(Color c) const { + return byColorBB[c]; +} + +inline Bitboard Position::pieces(Color c, PieceType pt) const { + return pieces(c) & pieces(pt); +} + +inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const { + return pieces(c) & (pieces(pt1) | pieces(pt2)); +} + +template inline int Position::count(Color c) const { + return pieceCount[make_piece(c, Pt)]; +} + +template inline int Position::count() const { + return count(WHITE) + count(BLACK); +} + +template inline const Square* Position::squares(Color c) const { + return pieceList[make_piece(c, Pt)]; +} + +template inline Square Position::square(Color c) const { + assert(pieceCount[make_piece(c, Pt)] == 1); + return squares(c)[0]; +} + +inline Square Position::ep_square() const { + return st->epSquare; +} + +inline bool Position::is_on_semiopen_file(Color c, Square s) const { + return !(pieces(c, PAWN) & file_bb(s)); +} + +inline bool Position::can_castle(CastlingRights cr) const { + return st->castlingRights & cr; +} + +inline CastlingRights Position::castling_rights(Color c) const { + return c & CastlingRights(st->castlingRights); +} + +inline bool Position::castling_impeded(CastlingRights cr) const { + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + + return pieces() & castlingPath[cr]; +} + +inline Square Position::castling_rook_square(CastlingRights cr) const { + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + + return castlingRookSquare[cr]; +} + +inline Bitboard Position::attackers_to(Square s) const { + return attackers_to(s, pieces()); +} + +inline Bitboard Position::checkers() const { + return st->checkersBB; +} + +inline Bitboard Position::blockers_for_king(Color c) const { + return st->blockersForKing[c]; +} + +inline Bitboard Position::pinners(Color c) const { + return st->pinners[c]; +} + +inline Bitboard Position::check_squares(PieceType pt) const { + return st->checkSquares[pt]; +} + +inline bool Position::is_discovery_check_on_king(Color c, Move m) const { + return st->blockersForKing[c] & from_sq(m); +} + +inline bool Position::pawn_passed(Color c, Square s) const { + return !(pieces(~c, PAWN) & passed_pawn_span(c, s)); +} + +inline bool Position::advanced_pawn_push(Move m) const { + return type_of(moved_piece(m)) == PAWN + && relative_rank(sideToMove, to_sq(m)) > RANK_5; +} + +inline int Position::pawns_on_same_color_squares(Color c, Square s) const { + return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares)); +} + +inline Key Position::key() const { + return st->key; +} + +inline Key Position::pawn_key() const { + return st->pawnKey; +} + +inline Key Position::material_key() const { + return st->materialKey; +} + +inline Score Position::psq_score() const { + return psq; +} + +inline Value Position::non_pawn_material(Color c) const { + return st->nonPawnMaterial[c]; +} + +inline Value Position::non_pawn_material() const { + return non_pawn_material(WHITE) + non_pawn_material(BLACK); +} + +inline int Position::game_ply() const { + return gamePly; +} + +inline int Position::rule50_count() const { + return st->rule50; +} + +inline bool Position::opposite_bishops() const { + return count(WHITE) == 1 + && count(BLACK) == 1 + && opposite_colors(square(WHITE), square(BLACK)); +} + +inline bool Position::is_chess960() const { + return chess960; +} + +inline bool Position::capture_or_promotion(Move m) const { + assert(is_ok(m)); + return type_of(m) != NORMAL ? type_of(m) != CASTLING : !empty(to_sq(m)); +} + +inline bool Position::capture(Move m) const { + assert(is_ok(m)); + // Castling is encoded as "king captures rook" + return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == ENPASSANT; +} + +inline Piece Position::captured_piece() const { + return st->capturedPiece; +} + +inline Thread* Position::this_thread() const { + return thisThread; +} + +inline void Position::put_piece(Piece pc, Square s) { + + board[s] = pc; + byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; + byColorBB[color_of(pc)] |= s; + index[s] = pieceCount[pc]++; + pieceList[pc][index[s]] = s; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; + psq += PSQT::psq[pc][s]; +} + +inline void Position::remove_piece(Square s) { + + // WARNING: This is not a reversible operation. If we remove a piece in + // do_move() and then replace it in undo_move() we will put it at the end of + // the list and not in its original place, it means index[] and pieceList[] + // are not invariant to a do_move() + undo_move() sequence. + Piece pc = board[s]; + byTypeBB[ALL_PIECES] ^= s; + byTypeBB[type_of(pc)] ^= s; + byColorBB[color_of(pc)] ^= s; + /* board[s] = NO_PIECE; Not needed, overwritten by the capturing one */ + Square lastSquare = pieceList[pc][--pieceCount[pc]]; + index[lastSquare] = index[s]; + pieceList[pc][index[lastSquare]] = lastSquare; + pieceList[pc][pieceCount[pc]] = SQ_NONE; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; + psq -= PSQT::psq[pc][s]; +} + +inline void Position::move_piece(Square from, Square to) { + + // index[from] is not updated and becomes stale. This works as long as index[] + // is accessed just by known occupied squares. + Piece pc = board[from]; + Bitboard fromTo = from | to; + byTypeBB[ALL_PIECES] ^= fromTo; + byTypeBB[type_of(pc)] ^= fromTo; + byColorBB[color_of(pc)] ^= fromTo; + board[from] = NO_PIECE; + board[to] = pc; + index[to] = index[from]; + pieceList[pc][index[to]] = to; + psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; +} + +inline void Position::do_move(Move m, StateInfo& newSt) { + do_move(m, newSt, gives_check(m)); +} + +inline StateInfo* Position::state() const { + + return st; +} + +#endif // #ifndef POSITION_H_INCLUDED diff --git a/engines/stockfish13/psqt.cpp b/engines/stockfish13/psqt.cpp new file mode 100644 index 0000000..eb36e75 --- /dev/null +++ b/engines/stockfish13/psqt.cpp @@ -0,0 +1,122 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "types.h" +#include "bitboard.h" + +namespace PSQT { + +#define S(mg, eg) make_score(mg, eg) + +// Bonus[PieceType][Square / 2] contains Piece-Square scores. For each piece +// type on a given square a (middlegame, endgame) score pair is assigned. Table +// is defined for files A..D and white side: it is symmetric for black side and +// second half of the files. +constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { + { }, + { }, + { // Knight + { S(-175, -96), S(-92,-65), S(-74,-49), S(-73,-21) }, + { S( -77, -67), S(-41,-54), S(-27,-18), S(-15, 8) }, + { S( -61, -40), S(-17,-27), S( 6, -8), S( 12, 29) }, + { S( -35, -35), S( 8, -2), S( 40, 13), S( 49, 28) }, + { S( -34, -45), S( 13,-16), S( 44, 9), S( 51, 39) }, + { S( -9, -51), S( 22,-44), S( 58,-16), S( 53, 17) }, + { S( -67, -69), S(-27,-50), S( 4,-51), S( 37, 12) }, + { S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) } + }, + { // Bishop + { S(-53,-57), S( -5,-30), S( -8,-37), S(-23,-12) }, + { S(-15,-37), S( 8,-13), S( 19,-17), S( 4, 1) }, + { S( -7,-16), S( 21, -1), S( -5, -2), S( 17, 10) }, + { S( -5,-20), S( 11, -6), S( 25, 0), S( 39, 17) }, + { S(-12,-17), S( 29, -1), S( 22,-14), S( 31, 15) }, + { S(-16,-30), S( 6, 6), S( 1, 4), S( 11, 6) }, + { S(-17,-31), S(-14,-20), S( 5, -1), S( 0, 1) }, + { S(-48,-46), S( 1,-42), S(-14,-37), S(-23,-24) } + }, + { // Rook + { S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) }, + { S(-21,-12), S(-13, -9), S( -8, -1), S( 6, -2) }, + { S(-25, 6), S(-11, -8), S( -1, -2), S( 3, -6) }, + { S(-13, -6), S( -5, 1), S( -4, -9), S(-6, 7) }, + { S(-27, -5), S(-15, 8), S( -4, 7), S( 3, -6) }, + { S(-22, 6), S( -2, 1), S( 6, -7), S(12, 10) }, + { S( -2, 4), S( 12, 5), S( 16, 20), S(18, -5) }, + { S(-17, 18), S(-19, 0), S( -1, 19), S( 9, 13) } + }, + { // Queen + { S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) }, + { S(-3,-55), S( 5,-31), S( 8,-22), S(12, -4) }, + { S(-3,-39), S( 6,-18), S(13, -9), S( 7, 3) }, + { S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) }, + { S( 0,-29), S(14, -6), S(12, 9), S( 5, 21) }, + { S(-4,-38), S(10,-18), S( 6,-12), S( 8, 1) }, + { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) }, + { S(-2,-75), S(-2,-52), S( 1,-43), S(-2,-36) } + }, + { // King + { S(271, 1), S(327, 45), S(271, 85), S(198, 76) }, + { S(278, 53), S(303,100), S(234,133), S(179,135) }, + { S(195, 88), S(258,130), S(169,169), S(120,175) }, + { S(164,103), S(190,156), S(138,172), S( 98,172) }, + { S(154, 96), S(179,166), S(105,199), S( 70,199) }, + { S(123, 92), S(145,172), S( 81,184), S( 31,191) }, + { S( 88, 47), S(120,121), S( 65,116), S( 33,131) }, + { S( 59, 11), S( 89, 59), S( 45, 73), S( -1, 78) } + } +}; + +constexpr Score PBonus[RANK_NB][FILE_NB] = + { // Pawn (asymmetric distribution) + { }, + { S( 3,-10), S( 3, -6), S( 10, 10), S( 19, 0), S( 16, 14), S( 19, 7), S( 7, -5), S( -5,-19) }, + { S( -9,-10), S(-15,-10), S( 11,-10), S( 15, 4), S( 32, 4), S( 22, 3), S( 5, -6), S(-22, -4) }, + { S( -4, 6), S(-23, -2), S( 6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S( 4,-10), S( -8, -9) }, + { S( 13, 10), S( 0, 5), S(-13, 4), S( 1, -5), S( 11, -5), S( -2, -5), S(-13, 14), S( 5, 9) }, + { S( 5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5, 7), S(-15, 6), S( -8, 13) }, + { S( -7, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) } + }; + +#undef S + +Score psq[PIECE_NB][SQUARE_NB]; + + +// PSQT::init() initializes piece-square tables: the white halves of the tables are +// copied from Bonus[] and PBonus[], adding the piece value, then the black halves of +// the tables are initialized by flipping and changing the sign of the white scores. +void init() { + + for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING}) + { + Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); + + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + File f = File(edge_distance(file_of(s))); + psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)] + : Bonus[pc][rank_of(s)][f]); + psq[~pc][flip_rank(s)] = -psq[pc][s]; + } + } +} + +} // namespace PSQT diff --git a/engines/stockfish13/search.cpp b/engines/stockfish13/search.cpp new file mode 100644 index 0000000..000785a --- /dev/null +++ b/engines/stockfish13/search.cpp @@ -0,0 +1,2007 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include // For std::memset +#include +#include + +#include "evaluate.h" +#include "misc.h" +#include "movegen.h" +#include "movepick.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "timeman.h" +#include "tt.h" +#include "uci.h" +#include "syzygy/tbprobe.h" + +#include "engineids.h" +void engine_message(int eid, const std::string& s); + +namespace Search { + + LimitsType Limits; +} + +namespace Tablebases { + + int Cardinality; + bool RootInTB; + bool UseRule50; + Depth ProbeDepth; +} + +namespace TB = Tablebases; + +using std::string; +using Eval::evaluate; +using namespace Search; + + +namespace { + + // Different node types, used as a template parameter + enum NodeType { NonPV, PV }; + + constexpr uint64_t TtHitAverageWindow = 4096; + constexpr uint64_t TtHitAverageResolution = 1024; + + // Razor and futility margins + constexpr int RazorMargin = 510; + Value futility_margin(Depth d, bool improving) { + return Value(223 * (d - improving)); + } + + // Reductions lookup table, initialized at startup + int Reductions[MAX_MOVES]; // [depth or moveNumber] + + Depth reduction(bool i, Depth d, int mn) { + int r = Reductions[d] * Reductions[mn]; + return (r + 509) / 1024 + (!i && r > 894); + } + + constexpr int futility_move_count(bool improving, Depth depth) { + return (3 + depth * depth) / (2 - improving); + } + + // History and stats update bonus, based on depth + int stat_bonus(Depth d) { + return d > 13 ? 29 : 17 * d * d + 134 * d - 134; + } + + // Add a small random component to draw evaluations to avoid 3fold-blindness + Value value_draw(Thread* thisThread) { + return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1); + } + + // Skill structure is used to implement strength limit + struct Skill { + explicit Skill(int l) : level(l) {} + bool enabled() const { return level < 20; } + bool time_to_pick(Depth depth) const { return depth == 1 + level; } + Move pick_best(size_t multiPV); + + int level; + Move best = MOVE_NONE; + }; + + // Breadcrumbs are used to mark nodes as being searched by a given thread + struct Breadcrumb { + std::atomic thread; + std::atomic key; + }; + std::array breadcrumbs; + + // ThreadHolding structure keeps track of which thread left breadcrumbs at the given + // node for potential reductions. A free node will be marked upon entering the moves + // loop by the constructor, and unmarked upon leaving that loop by the destructor. + struct ThreadHolding { + explicit ThreadHolding(Thread* thisThread, Key posKey, int ply) { + location = ply < 8 ? &breadcrumbs[posKey & (breadcrumbs.size() - 1)] : nullptr; + otherThread = false; + owning = false; + if (location) + { + // See if another already marked this location, if not, mark it ourselves + Thread* tmp = (*location).thread.load(std::memory_order_relaxed); + if (tmp == nullptr) + { + (*location).thread.store(thisThread, std::memory_order_relaxed); + (*location).key.store(posKey, std::memory_order_relaxed); + owning = true; + } + else if ( tmp != thisThread + && (*location).key.load(std::memory_order_relaxed) == posKey) + otherThread = true; + } + } + + ~ThreadHolding() { + if (owning) // Free the marked location + (*location).thread.store(nullptr, std::memory_order_relaxed); + } + + bool marked() { return otherThread; } + + private: + Breadcrumb* location; + bool otherThread, owning; + }; + + template + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); + + template + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); + + Value value_to_tt(Value v, int ply); + Value value_from_tt(Value v, int ply, int r50c); + void update_pv(Move* pv, Move move, Move* childPv); + void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); + void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus, int depth); + void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, + Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth); + + // perft() is our utility to verify move generation. All the leaf nodes up + // to the given depth are generated and counted, and the sum is returned. + template + uint64_t sf_perft(Position& pos, Depth depth) { + + StateInfo st; + uint64_t cnt, nodes = 0; + const bool leaf = (depth == 2); + + for (const auto& m : MoveList(pos)) + { + if (Root && depth <= 1) + cnt = 1, nodes++; + else + { + pos.do_move(m, st); + cnt = leaf ? MoveList(pos).size() : sf_perft(pos, depth - 1); + nodes += cnt; + pos.undo_move(m); + } + if (Root) { +// sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + engine_message(stockfish, UCI::move(m, pos.is_chess960()) + ": " + std::to_string(cnt)); + } + } + return nodes; + } + +} // namespace + + +/// Search::init() is called at startup to initialize various lookup tables + +void Search::init() { + + for (int i = 1; i < MAX_MOVES; ++i) + Reductions[i] = int((22.0 + std::log(Threads.size())) * std::log(i)); +} + + +/// Search::clear() resets search state to its initial value + +void Search::clear() { + + Threads.main()->wait_for_search_finished(); + + Time.availableNodes = 0; + TT.clear(); + Threads.clear(); + Tablebases::init(Options["SyzygyPath"]); // Free mapped files +} + + +/// MainThread::search() is started when the program receives the UCI 'go' +/// command. It searches from the root position and outputs the "bestmove". + +void MainThread::search() { + + if (Limits.perft) + { + nodes = sf_perft(rootPos, Limits.perft); +// sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + engine_message(stockfish, "\nNodes searched: " + std::to_string(nodes)); + return; + } + + Color us = rootPos.side_to_move(); + Time.init(Limits, us, rootPos.game_ply()); + TT.new_search(); + + Eval::verify_NNUE(); + + if (rootMoves.empty()) + { + rootMoves.emplace_back(MOVE_NONE); +// sync_cout << "info depth 0 score " +// << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) +// << sync_endl; + engine_message(stockfish, "info depth 0 score " + UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW)); + + } + else + { + Threads.start_searching(); // start non-main threads + Thread::search(); // main thread start searching + } + + // When we reach the maximum depth, we can arrive here without a raise of + // Threads.stop. However, if we are pondering or in an infinite search, + // the UCI protocol states that we shouldn't print the best move before the + // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here + // until the GUI sends one of those commands. + + while (!Threads.stop && (ponder || Limits.infinite)) + {} // Busy wait for a stop or a ponder reset + + // Stop the threads if not already stopped (also raise the stop if + // "ponderhit" just reset Threads.ponder). + Threads.stop = true; + + // Wait until all threads have finished + Threads.wait_for_search_finished(); + + // When playing in 'nodes as time' mode, subtract the searched nodes from + // the available ones before exiting. + if (Limits.npmsec) + Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); + + Thread* bestThread = this; + + if ( int(Options["MultiPV"]) == 1 + && !Limits.depth + && !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"])) + && rootMoves[0].pv[0] != MOVE_NONE) + bestThread = Threads.get_best_thread(); + + bestPreviousScore = bestThread->rootMoves[0].score; + + // Send again PV info if we have a new best thread + if (bestThread != this) { +// sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl; + engine_message(stockfish, UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE)); + + } + +// sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); +// +// if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) +// std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); +// +// std::cout << sync_endl; + + auto s = "bestmove " + UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) + s += " ponder " + UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + engine_message(stockfish, s); + +} + + +/// Thread::search() is the main iterative deepening loop. It calls search() +/// repeatedly with increasing depth until the allocated thinking time has been +/// consumed, the user stops the search, or the maximum search depth is reached. + +void Thread::search() { + + // To allow access to (ss-7) up to (ss+2), the stack must be oversized. + // The former is needed to allow update_continuation_histories(ss-1, ...), + // which accesses its argument at ss-6, also near the root. + // The latter is needed for statScores and killer initialization. + Stack stack[MAX_PLY+10], *ss = stack+7; + Move pv[MAX_PLY+1]; + Value bestValue, alpha, beta, delta; + Move lastBestMove = MOVE_NONE; + Depth lastBestMoveDepth = 0; + MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); + double timeReduction = 1, totBestMoveChanges = 0; + Color us = rootPos.side_to_move(); + int iterIdx = 0; + + std::memset(ss-7, 0, 10 * sizeof(Stack)); + for (int i = 7; i > 0; i--) + (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel + + ss->pv = pv; + + bestValue = delta = alpha = -VALUE_INFINITE; + beta = VALUE_INFINITE; + + if (mainThread) + { + if (mainThread->bestPreviousScore == VALUE_INFINITE) + for (int i = 0; i < 4; ++i) + mainThread->iterValue[i] = VALUE_ZERO; + else + for (int i = 0; i < 4; ++i) + mainThread->iterValue[i] = mainThread->bestPreviousScore; + } + + std::copy(&lowPlyHistory[2][0], &lowPlyHistory.back().back() + 1, &lowPlyHistory[0][0]); + std::fill(&lowPlyHistory[MAX_LPH - 2][0], &lowPlyHistory.back().back() + 1, 0); + + size_t multiPV = size_t(Options["MultiPV"]); + + // Pick integer skill levels, but non-deterministically round up or down + // such that the average integer skill corresponds to the input floating point one. + // UCI_Elo is converted to a suitable fractional skill level, using anchoring + // to CCRL Elo (goldfish 1.13 = 2000) and a fit through Ordo derived Elo + // for match (TC 60+0.6) results spanning a wide range of k values. + PRNG rng(now()); + double floatLevel = Options["UCI_LimitStrength"] ? + std::clamp(std::pow((Options["UCI_Elo"] - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0) : + double(Options["Skill Level"]); + int intLevel = int(floatLevel) + + ((floatLevel - int(floatLevel)) * 1024 > rng.rand() % 1024 ? 1 : 0); + Skill skill(intLevel); + + // When playing with strength handicap enable MultiPV search that we will + // use behind the scenes to retrieve a set of possible moves. + if (skill.enabled()) + multiPV = std::max(multiPV, (size_t)4); + + multiPV = std::min(multiPV, rootMoves.size()); + ttHitAverage = TtHitAverageWindow * TtHitAverageResolution / 2; + + int ct = int(Options["Contempt"]) * PawnValueEg / 100; // From centipawns + + // In analysis mode, adjust contempt in accordance with user preference + if (Limits.infinite || Options["UCI_AnalyseMode"]) + ct = Options["Analysis Contempt"] == "Off" ? 0 + : Options["Analysis Contempt"] == "Both" ? ct + : Options["Analysis Contempt"] == "White" && us == BLACK ? -ct + : Options["Analysis Contempt"] == "Black" && us == WHITE ? -ct + : ct; + + // Evaluation score is from the white point of view + contempt = (us == WHITE ? make_score(ct, ct / 2) + : -make_score(ct, ct / 2)); + + int searchAgainCounter = 0; + + // Iterative deepening loop until requested to stop or the target depth is reached + while ( ++rootDepth < MAX_PLY + && !Threads.stop + && !(Limits.depth && mainThread && rootDepth > Limits.depth)) + { + // Age out PV variability metric + if (mainThread) + totBestMoveChanges /= 2; + + // Save the last iteration's scores before first PV line is searched and + // all the move scores except the (new) PV are set to -VALUE_INFINITE. + for (RootMove& rm : rootMoves) + rm.previousScore = rm.score; + + size_t pvFirst = 0; + pvLast = 0; + + if (!Threads.increaseDepth) + searchAgainCounter++; + + // MultiPV loop. We perform a full root search for each PV line + for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) + { + if (pvIdx == pvLast) + { + pvFirst = pvLast; + for (pvLast++; pvLast < rootMoves.size(); pvLast++) + if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank) + break; + } + + // Reset UCI info selDepth for each depth and each PV line + selDepth = 0; + + // Reset aspiration window starting size + if (rootDepth >= 4) + { + Value prev = rootMoves[pvIdx].previousScore; + delta = Value(17); + alpha = std::max(prev - delta,-VALUE_INFINITE); + beta = std::min(prev + delta, VALUE_INFINITE); + + // Adjust contempt based on root move's previousScore (dynamic contempt) + int dct = ct + (105 - ct / 2) * prev / (abs(prev) + 149); + + contempt = (us == WHITE ? make_score(dct, dct / 2) + : -make_score(dct, dct / 2)); + } + + // Start with a small aspiration window and, in the case of a fail + // high/low, re-search with a bigger window until we don't fail + // high/low anymore. + int failedHighCnt = 0; + while (true) + { + Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter); + bestValue = ::search(rootPos, ss, alpha, beta, adjustedDepth, false); + + // Bring the best move to the front. It is critical that sorting + // is done with a stable algorithm because all the values but the + // first and eventually the new best one are set to -VALUE_INFINITE + // and we want to keep the same order for all the moves except the + // new PV that goes to the front. Note that in case of MultiPV + // search the already searched PV lines are preserved. + std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); + + // If search has been stopped, we break immediately. Sorting is + // safe because RootMoves is still valid, although it refers to + // the previous iteration. + if (Threads.stop) + break; + + // When failing high/low give some update (without cluttering + // the UI) before a re-search. + if ( mainThread + && multiPV == 1 + && (bestValue <= alpha || bestValue >= beta) + && Time.elapsed() > 3000) { +// sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; + engine_message(stockfish, UCI::pv(rootPos, rootDepth, alpha, beta)); + + } + + // In case of failing low/high increase aspiration window and + // re-search, otherwise exit the loop. + if (bestValue <= alpha) + { + beta = (alpha + beta) / 2; + alpha = std::max(bestValue - delta, -VALUE_INFINITE); + + failedHighCnt = 0; + if (mainThread) + mainThread->stopOnPonderhit = false; + } + else if (bestValue >= beta) + { + beta = std::min(bestValue + delta, VALUE_INFINITE); + ++failedHighCnt; + } + else + { + ++rootMoves[pvIdx].bestMoveCount; + break; + } + + delta += delta / 4 + 5; + + assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + } + + // Sort the PV lines searched so far and update the GUI + std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); + + if ( mainThread + && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) { +// sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; + engine_message(stockfish, UCI::pv(rootPos, rootDepth, alpha, beta)); + } + } + + if (!Threads.stop) + completedDepth = rootDepth; + + if (rootMoves[0].pv[0] != lastBestMove) { + lastBestMove = rootMoves[0].pv[0]; + lastBestMoveDepth = rootDepth; + } + + // Have we found a "mate in x"? + if ( Limits.mate + && bestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - bestValue <= 2 * Limits.mate) + Threads.stop = true; + + if (!mainThread) + continue; + + // If skill level is enabled and time is up, pick a sub-optimal best move + if (skill.enabled() && skill.time_to_pick(rootDepth)) + skill.pick_best(multiPV); + + // Do we have time for the next iteration? Can we stop searching now? + if ( Limits.use_time_management() + && !Threads.stop + && !mainThread->stopOnPonderhit) + { + double fallingEval = (318 + 6 * (mainThread->bestPreviousScore - bestValue) + + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 825.0; + fallingEval = std::clamp(fallingEval, 0.5, 1.5); + + // If the bestMove is stable over several iterations, reduce time accordingly + timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.92 : 0.95; + double reduction = (1.47 + mainThread->previousTimeReduction) / (2.32 * timeReduction); + + // Use part of the gained time from a previous stable move for the current move + for (Thread* th : Threads) + { + totBestMoveChanges += th->bestMoveChanges; + th->bestMoveChanges = 0; + } + double bestMoveInstability = 1 + 2 * totBestMoveChanges / Threads.size(); + + double totalTime = rootMoves.size() == 1 ? 0 : + Time.optimum() * fallingEval * reduction * bestMoveInstability; + + // Stop the search if we have exceeded the totalTime, at least 1ms search + if (Time.elapsed() > totalTime) + { + // If we are allowed to ponder do not stop the search now but + // keep pondering until the GUI sends "ponderhit" or "stop". + if (mainThread->ponder) + mainThread->stopOnPonderhit = true; + else + Threads.stop = true; + } + else if ( Threads.increaseDepth + && !mainThread->ponder + && Time.elapsed() > totalTime * 0.58) + Threads.increaseDepth = false; + else + Threads.increaseDepth = true; + } + + mainThread->iterValue[iterIdx] = bestValue; + iterIdx = (iterIdx + 1) & 3; + } + + if (!mainThread) + return; + + mainThread->previousTimeReduction = timeReduction; + + // If skill level is enabled, swap best PV line with the sub-optimal one + if (skill.enabled()) + std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(multiPV))); +} + + +namespace { + + // search<>() is the main search function for both PV and non-PV nodes + + template + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { + + constexpr bool PvNode = NT == PV; + const bool rootNode = PvNode && ss->ply == 0; + + // Check if we have an upcoming move which draws by repetition, or + // if the opponent had an alternative move earlier to this position. + if ( pos.rule50_count() >= 3 + && alpha < VALUE_DRAW + && !rootNode + && pos.has_game_cycle(ss->ply)) + { + alpha = value_draw(pos.this_thread()); + if (alpha >= beta) + return alpha; + } + + // Dive into quiescence search when the depth reaches zero + if (depth <= 0) + return qsearch(pos, ss, alpha, beta); + + assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); + assert(PvNode || (alpha == beta - 1)); + assert(0 < depth && depth < MAX_PLY); + assert(!(PvNode && cutNode)); + + Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; + StateInfo st; + TTEntry* tte; + Key posKey; + Move ttMove, move, excludedMove, bestMove; + Depth extension, newDepth; + Value bestValue, value, ttValue, eval, maxValue, probCutBeta; + bool formerPv, givesCheck, improving, didLMR, priorCapture; + bool captureOrPromotion, doFullDepthSearch, moveCountPruning, + ttCapture, singularQuietLMR; + Piece movedPiece; + int moveCount, captureCount, quietCount; + + // Step 1. Initialize node + Thread* thisThread = pos.this_thread(); + ss->inCheck = pos.checkers(); + priorCapture = pos.captured_piece(); + Color us = pos.side_to_move(); + moveCount = captureCount = quietCount = ss->moveCount = 0; + bestValue = -VALUE_INFINITE; + maxValue = VALUE_INFINITE; + + Eval::evalHits = Eval::nnueHits = 0; + + // Check for the available remaining time + if (thisThread == Threads.main()) + static_cast(thisThread)->check_time(); + + // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) + if (PvNode && thisThread->selDepth < ss->ply + 1) + thisThread->selDepth = ss->ply + 1; + + if (!rootNode) + { + // Step 2. Check for aborted search and immediate draw + if ( Threads.stop.load(std::memory_order_relaxed) + || pos.is_draw(ss->ply) + || ss->ply >= MAX_PLY) + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) + : value_draw(pos.this_thread()); + + // Step 3. Mate distance pruning. Even if we mate at the next move our score + // would be at best mate_in(ss->ply+1), but if alpha is already bigger because + // a shorter mate was found upward in the tree then there is no need to search + // because we will never beat the current alpha. Same logic but with reversed + // signs applies also in the opposite condition of being mated instead of giving + // mate. In this case return a fail-high score. + alpha = std::max(mated_in(ss->ply), alpha); + beta = std::min(mate_in(ss->ply+1), beta); + if (alpha >= beta) + return alpha; + } + + assert(0 <= ss->ply && ss->ply < MAX_PLY); + + (ss+1)->ply = ss->ply + 1; + (ss+1)->ttPv = false; + (ss+1)->excludedMove = bestMove = MOVE_NONE; + (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + Square prevSq = to_sq((ss-1)->currentMove); + + // Initialize statScore to zero for the grandchildren of the current position. + // So statScore is shared between all grandchildren and only the first grandchild + // starts with statScore = 0. Later grandchildren start with the last calculated + // statScore of the previous grandchild. This influences the reduction rules in + // LMR which are based on the statScore of parent position. + if (!rootNode) + (ss+2)->statScore = 0; + + // Step 4. Transposition table lookup. We don't want the score of a partial + // search to overwrite a previous full search TT value, so we use a different + // position key in case of an excluded move. + excludedMove = ss->excludedMove; + posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove); + tte = TT.probe(posKey, ss->ttHit); + ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; + ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] + : ss->ttHit ? tte->move() : MOVE_NONE; + if (!excludedMove) + ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); + formerPv = ss->ttPv && !PvNode; + + if ( ss->ttPv + && depth > 12 + && ss->ply - 1 < MAX_LPH + && !priorCapture + && is_ok((ss-1)->currentMove)) + thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5); + + // thisThread->ttHitAverage can be used to approximate the running average of ttHit + thisThread->ttHitAverage = (TtHitAverageWindow - 1) * thisThread->ttHitAverage / TtHitAverageWindow + + TtHitAverageResolution * ss->ttHit; + + // At non-PV nodes we check for an early TT cutoff + if ( !PvNode + && ss->ttHit + && tte->depth() >= depth + && ttValue != VALUE_NONE // Possible in case of TT access race + && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) + : (tte->bound() & BOUND_UPPER))) + { + // If ttMove is quiet, update move sorting heuristics on TT hit + if (ttMove) + { + if (ttValue >= beta) + { + if (!pos.capture_or_promotion(ttMove)) + update_quiet_stats(pos, ss, ttMove, stat_bonus(depth), depth); + + // Extra penalty for early quiet moves of the previous ply + if ((ss-1)->moveCount <= 2 && !priorCapture) + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); + } + // Penalty for a quiet ttMove that fails low + else if (!pos.capture_or_promotion(ttMove)) + { + int penalty = -stat_bonus(depth); + thisThread->mainHistory[us][from_to(ttMove)] << penalty; + update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); + } + } + + if (pos.rule50_count() < 90) + return ttValue; + } + + // Step 5. Tablebases probe + if (!rootNode && TB::Cardinality) + { + int piecesCount = pos.count(); + + if ( piecesCount <= TB::Cardinality + && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) + && pos.rule50_count() == 0 + && !pos.can_castle(ANY_CASTLING)) + { + TB::ProbeState err; + TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); + + // Force check of time on the next occasion + if (thisThread == Threads.main()) + static_cast(thisThread)->callsCnt = 0; + + if (err != TB::ProbeState::FAIL) + { + thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); + + int drawScore = TB::UseRule50 ? 1 : 0; + + // use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score + value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 + : wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1 + : VALUE_DRAW + 2 * wdl * drawScore; + + Bound b = wdl < -drawScore ? BOUND_UPPER + : wdl > drawScore ? BOUND_LOWER : BOUND_EXACT; + + if ( b == BOUND_EXACT + || (b == BOUND_LOWER ? value >= beta : value <= alpha)) + { + tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, + std::min(MAX_PLY - 1, depth + 6), + MOVE_NONE, VALUE_NONE); + + return value; + } + + if (PvNode) + { + if (b == BOUND_LOWER) + bestValue = value, alpha = std::max(alpha, bestValue); + else + maxValue = value; + } + } + } + } + + CapturePieceToHistory& captureHistory = thisThread->captureHistory; + + // Step 6. Static evaluation of the position + if (ss->inCheck) + { + // Skip early pruning when in check + ss->staticEval = eval = VALUE_NONE; + improving = false; + goto moves_loop; + } + else if (ss->ttHit) + { + // Never assume anything about values stored in TT + ss->staticEval = eval = tte->eval(); + if (eval == VALUE_NONE) + ss->staticEval = eval = evaluate(pos); + + if (eval == VALUE_DRAW) + eval = value_draw(thisThread); + + // Can ttValue be used as a better position evaluation? + if ( ttValue != VALUE_NONE + && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) + eval = ttValue; + } + else + { + if ((ss-1)->currentMove != MOVE_NULL) + ss->staticEval = eval = evaluate(pos); + else + ss->staticEval = eval = -(ss-1)->staticEval + 2 * Tempo; + + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); + } + + // Step 7. Razoring (~1 Elo) + if ( !rootNode // The required rootNode PV handling is not available in qsearch + && depth == 1 + && eval <= alpha - RazorMargin) + return qsearch(pos, ss, alpha, beta); + + improving = (ss-2)->staticEval == VALUE_NONE + ? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE + : ss->staticEval > (ss-2)->staticEval; + + // Step 8. Futility pruning: child node (~50 Elo) + if ( !PvNode + && depth < 8 + && eval - futility_margin(depth, improving) >= beta + && eval < VALUE_KNOWN_WIN) // Do not return unproven wins + return eval; + + // Step 9. Null move search with verification search (~40 Elo) + if ( !PvNode + && (ss-1)->currentMove != MOVE_NULL + && (ss-1)->statScore < 22977 + && eval >= beta + && eval >= ss->staticEval + && ss->staticEval >= beta - 30 * depth - 28 * improving + 84 * ss->ttPv + 182 + && !excludedMove + && pos.non_pawn_material(us) + && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) + { + assert(eval - beta >= 0); + + // Null move dynamic reduction based on depth and value + Depth R = (817 + 71 * depth) / 213 + std::min(int(eval - beta) / 192, 3); + + ss->currentMove = MOVE_NULL; + ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; + + pos.do_null_move(st); + + Value nullValue = -search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); + + pos.undo_null_move(); + + if (nullValue >= beta) + { + // Do not return unproven mate or TB scores + if (nullValue >= VALUE_TB_WIN_IN_MAX_PLY) + nullValue = beta; + + if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 13)) + return nullValue; + + assert(!thisThread->nmpMinPly); // Recursive verification is not allowed + + // Do verification search at high depths, with null move pruning disabled + // for us, until ply exceeds nmpMinPly. + thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; + thisThread->nmpColor = us; + + Value v = search(pos, ss, beta-1, beta, depth-R, false); + + thisThread->nmpMinPly = 0; + + if (v >= beta) + return nullValue; + } + } + + probCutBeta = beta + 176 - 49 * improving; + + // Step 10. ProbCut (~10 Elo) + // If we have a good enough capture and a reduced search returns a value + // much above beta, we can (almost) safely prune the previous move. + if ( !PvNode + && depth > 4 + && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + // if value from transposition table is lower than probCutBeta, don't attempt probCut + // there and in further interactions with transposition table cutoff depth is set to depth - 3 + // because probCut search has depth set to depth - 4 but we also do a move before it + // so effective depth is equal to depth - 3 + && !( ss->ttHit + && tte->depth() >= depth - 3 + && ttValue != VALUE_NONE + && ttValue < probCutBeta)) + { + // if ttMove is a capture and value from transposition table is good enough produce probCut + // cutoff without digging into actual probCut search + if ( ss->ttHit + && tte->depth() >= depth - 3 + && ttValue != VALUE_NONE + && ttValue >= probCutBeta + && ttMove + && pos.capture_or_promotion(ttMove)) + return probCutBeta; + + assert(probCutBeta < VALUE_INFINITE); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); + int probCutCount = 0; + bool ttPv = ss->ttPv; + ss->ttPv = false; + + while ( (move = mp.next_move()) != MOVE_NONE + && probCutCount < 2 + 2 * cutNode) + if (move != excludedMove && pos.legal(move)) + { + assert(pos.capture_or_promotion(move)); + assert(depth >= 5); + + captureOrPromotion = true; + probCutCount++; + + ss->currentMove = move; + ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] + [captureOrPromotion] + [pos.moved_piece(move)] + [to_sq(move)]; + + pos.do_move(move, st); + + // Perform a preliminary qsearch to verify that the move holds + value = -qsearch(pos, ss+1, -probCutBeta, -probCutBeta+1); + + // If the qsearch held, perform the regular search + if (value >= probCutBeta) + value = -search(pos, ss+1, -probCutBeta, -probCutBeta+1, depth - 4, !cutNode); + + pos.undo_move(move); + + if (value >= probCutBeta) + { + // if transposition table doesn't have equal or more deep info write probCut data into it + if ( !(ss->ttHit + && tte->depth() >= depth - 3 + && ttValue != VALUE_NONE)) + tte->save(posKey, value_to_tt(value, ss->ply), ttPv, + BOUND_LOWER, + depth - 3, move, ss->staticEval); + return value; + } + } + ss->ttPv = ttPv; + } + + // Step 11. If the position is not in TT, decrease depth by 2 + if ( PvNode + && depth >= 6 + && !ttMove) + depth -= 2; + +moves_loop: // When in check, search starts from here + + const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, + nullptr , (ss-4)->continuationHistory, + nullptr , (ss-6)->continuationHistory }; + + Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; + + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, + &thisThread->lowPlyHistory, + &captureHistory, + contHist, + countermove, + ss->killers, + ss->ply); + + value = bestValue; + singularQuietLMR = moveCountPruning = false; + ttCapture = ttMove && pos.capture_or_promotion(ttMove); + + // Mark this node as being searched + ThreadHolding th(thisThread, posKey, ss->ply); + + // Step 12. Loop through all pseudo-legal moves until no moves remain + // or a beta cutoff occurs. + while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) + { + assert(is_ok(move)); + + if (move == excludedMove) + continue; + + // At root obey the "searchmoves" option and skip moves not listed in Root + // Move List. As a consequence any illegal move is also skipped. In MultiPV + // mode we also skip PV moves which have been already searched and those + // of lower "TB rank" if we are in a TB root position. + if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, + thisThread->rootMoves.begin() + thisThread->pvLast, move)) + continue; + + // Check for legality + if (!rootNode && !pos.legal(move)) + continue; + + ss->moveCount = ++moveCount; + + if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) { +// sync_cout << "info depth " << depth +// << " currmove " << UCI::move(move, pos.is_chess960()) +// << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; + auto s = "info depth " + std::to_string(depth) + + " currmove " + UCI::move(move, pos.is_chess960()) + + " currmovenumber " + std::to_string(moveCount + thisThread->pvIdx); + if (Eval::nnueHits && Eval::evalHits) { + s += " nnuehits " + std::to_string(static_cast(Eval::nnueHits * 1000 / Eval::evalHits)); + } + engine_message(stockfish, s); + } + + if (PvNode) + (ss+1)->pv = nullptr; + + extension = 0; + captureOrPromotion = pos.capture_or_promotion(move); + movedPiece = pos.moved_piece(move); + givesCheck = pos.gives_check(move); + + // Calculate new depth for this move + newDepth = depth - 1; + + // Step 13. Pruning at shallow depth (~200 Elo) + if ( !rootNode + && pos.non_pawn_material(us) + && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + { + // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold + moveCountPruning = moveCount >= futility_move_count(improving, depth); + + // Reduced depth of the next LMR search + int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0); + + if ( !captureOrPromotion + && !givesCheck) + { + // Countermoves based pruning (~20 Elo) + if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1) + && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold + && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold) + continue; + + // Futility pruning: parent node (~5 Elo) + if ( lmrDepth < 7 + && !ss->inCheck + && ss->staticEval + 283 + 170 * lmrDepth <= alpha + && (*contHist[0])[movedPiece][to_sq(move)] + + (*contHist[1])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] + + (*contHist[5])[movedPiece][to_sq(move)] / 2 < 27376) + continue; + + // Prune moves with negative SEE (~20 Elo) + if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth))) + continue; + } + else + { + // Capture history based pruning when the move doesn't give check + if ( !givesCheck + && lmrDepth < 1 + && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0) + continue; + + // Futility pruning for captures + if ( !givesCheck + && lmrDepth < 6 + && !(PvNode && abs(bestValue) < 2) + && !ss->inCheck + && ss->staticEval + 169 + 244 * lmrDepth + + PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] <= alpha) + continue; + + // See based pruning + if (!pos.see_ge(move, Value(-221) * depth)) // (~25 Elo) + continue; + } + } + + // Step 14. Extensions (~75 Elo) + + // Singular extension search (~70 Elo). If all moves but one fail low on a + // search of (alpha-s, beta-s), and just one fails high on (alpha, beta), + // then that move is singular and should be extended. To verify this we do + // a reduced search on all the other moves but the ttMove and if the + // result is lower than ttValue minus a margin, then we will extend the ttMove. + if ( depth >= 7 + && move == ttMove + && !rootNode + && !excludedMove // Avoid recursive singular search + /* && ttValue != VALUE_NONE Already implicit in the next condition */ + && abs(ttValue) < VALUE_KNOWN_WIN + && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 3) + { + Value singularBeta = ttValue - ((formerPv + 4) * depth) / 2; + Depth singularDepth = (depth - 1 + 3 * formerPv) / 2; + ss->excludedMove = move; + value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); + ss->excludedMove = MOVE_NONE; + + if (value < singularBeta) + { + extension = 1; + singularQuietLMR = !ttCapture; + } + + // Multi-cut pruning + // Our ttMove is assumed to fail high, and now we failed high also on a reduced + // search without the ttMove. So we assume this expected Cut-node is not singular, + // that multiple moves fail high, and we can prune the whole subtree by returning + // a soft bound. + else if (singularBeta >= beta) + return singularBeta; + + // If the eval of ttMove is greater than beta we try also if there is another + // move that pushes it over beta, if so also produce a cutoff. + else if (ttValue >= beta) + { + ss->excludedMove = move; + value = search(pos, ss, beta - 1, beta, (depth + 3) / 2, cutNode); + ss->excludedMove = MOVE_NONE; + + if (value >= beta) + return beta; + } + } + + // Check extension (~2 Elo) + else if ( givesCheck + && (pos.is_discovery_check_on_king(~us, move) || pos.see_ge(move))) + extension = 1; + + // Last captures extension + else if ( PieceValue[EG][pos.captured_piece()] > PawnValueEg + && pos.non_pawn_material() <= 2 * RookValueMg) + extension = 1; + + // Late irreversible move extension + if ( move == ttMove + && pos.rule50_count() > 80 + && (captureOrPromotion || type_of(movedPiece) == PAWN)) + extension = 2; + + // Add extension to new depth + newDepth += extension; + + // Speculative prefetch as early as possible + prefetch(TT.first_entry(pos.key_after(move))); + + // Update the current move (this must be done after singular extension search) + ss->currentMove = move; + ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] + [captureOrPromotion] + [movedPiece] + [to_sq(move)]; + + // Step 15. Make the move + pos.do_move(move, st, givesCheck); + + // Step 16. Reduced depth search (LMR, ~200 Elo). If the move fails high it will be + // re-searched at full depth. + if ( depth >= 3 + && moveCount > 1 + 2 * rootNode + && ( !captureOrPromotion + || moveCountPruning + || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha + || cutNode + || thisThread->ttHitAverage < 427 * TtHitAverageResolution * TtHitAverageWindow / 1024)) + { + Depth r = reduction(improving, depth, moveCount); + + // Decrease reduction if the ttHit running average is large + if (thisThread->ttHitAverage > 509 * TtHitAverageResolution * TtHitAverageWindow / 1024) + r--; + + // Reduction if other threads are searching this position + if (th.marked()) + r++; + + // Decrease reduction if position is or has been on the PV (~10 Elo) + if (ss->ttPv) + r -= 2; + + if (moveCountPruning && !formerPv) + r++; + + // Decrease reduction if opponent's move count is high (~5 Elo) + if ((ss-1)->moveCount > 13) + r--; + + // Decrease reduction if ttMove has been singularly extended (~3 Elo) + if (singularQuietLMR) + r--; + + if (!captureOrPromotion) + { + // Increase reduction if ttMove is a capture (~5 Elo) + if (ttCapture) + r++; + + // Increase reduction for cut nodes (~10 Elo) + if (cutNode) + r += 2; + + // Decrease reduction for moves that escape a capture. Filter out + // castling moves, because they are coded as "king captures rook" and + // hence break make_move(). (~2 Elo) + else if ( type_of(move) == NORMAL + && !pos.see_ge(reverse_move(move))) + r -= 2 + ss->ttPv - (type_of(movedPiece) == PAWN); + + ss->statScore = thisThread->mainHistory[us][from_to(move)] + + (*contHist[0])[movedPiece][to_sq(move)] + + (*contHist[1])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] + - 5287; + + // Decrease/increase reduction by comparing opponent's stat score (~10 Elo) + if (ss->statScore >= -106 && (ss-1)->statScore < -104) + r--; + + else if ((ss-1)->statScore >= -119 && ss->statScore < -140) + r++; + + // Decrease/increase reduction for moves with a good/bad history (~30 Elo) + r -= ss->statScore / 14884; + } + else + { + // Increase reduction for captures/promotions if late move and at low depth + if (depth < 8 && moveCount > 2) + r++; + + // Unless giving check, this capture is likely bad + if ( !givesCheck + && ss->staticEval + PieceValue[EG][pos.captured_piece()] + 213 * depth <= alpha) + r++; + } + + Depth d = std::clamp(newDepth - r, 1, newDepth); + + value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); + + doFullDepthSearch = value > alpha && d != newDepth; + + didLMR = true; + } + else + { + doFullDepthSearch = !PvNode || moveCount > 1; + + didLMR = false; + } + + // Step 17. Full depth search when LMR is skipped or fails high + if (doFullDepthSearch) + { + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); + + if (didLMR && !captureOrPromotion) + { + int bonus = value > alpha ? stat_bonus(newDepth) + : -stat_bonus(newDepth); + + if (move == ss->killers[0]) + bonus += bonus / 4; + + update_continuation_histories(ss, movedPiece, to_sq(move), bonus); + } + } + + // For PV nodes only, do a full PV search on the first move or after a fail + // high (in the latter case search only if value < beta), otherwise let the + // parent node fail low with value <= alpha and try another move. + if (PvNode && (moveCount == 1 || (value > alpha && (rootNode || value < beta)))) + { + (ss+1)->pv = pv; + (ss+1)->pv[0] = MOVE_NONE; + + value = -search(pos, ss+1, -beta, -alpha, newDepth, false); + } + + // Step 18. Undo move + pos.undo_move(move); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // Step 19. Check for a new best move + // Finished searching the move. If a stop occurred, the return value of + // the search cannot be trusted, and we return immediately without + // updating best move, PV and TT. + if (Threads.stop.load(std::memory_order_relaxed)) + return VALUE_ZERO; + + if (rootNode) + { + RootMove& rm = *std::find(thisThread->rootMoves.begin(), + thisThread->rootMoves.end(), move); + + // PV move or new best move? + if (moveCount == 1 || value > alpha) + { + rm.score = value; + rm.selDepth = thisThread->selDepth; + rm.pv.resize(1); + + assert((ss+1)->pv); + + for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m) + rm.pv.push_back(*m); + + // We record how often the best move has been changed in each + // iteration. This information is used for time management: when + // the best move changes frequently, we allocate some more time. + if (moveCount > 1) + ++thisThread->bestMoveChanges; + } + else + // All other moves but the PV are set to the lowest value: this + // is not a problem when sorting because the sort is stable and the + // move position in the list is preserved - just the PV is pushed up. + rm.score = -VALUE_INFINITE; + } + + if (value > bestValue) + { + bestValue = value; + + if (value > alpha) + { + bestMove = move; + + if (PvNode && !rootNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss+1)->pv); + + if (PvNode && value < beta) // Update alpha! Always alpha < beta + alpha = value; + else + { + assert(value >= beta); // Fail high + ss->statScore = 0; + break; + } + } + } + + if (move != bestMove) + { + if (captureOrPromotion && captureCount < 32) + capturesSearched[captureCount++] = move; + + else if (!captureOrPromotion && quietCount < 64) + quietsSearched[quietCount++] = move; + } + } + + // The following condition would detect a stop only after move loop has been + // completed. But in this case bestValue is valid because we have fully + // searched our subtree, and we can anyhow save the result in TT. + /* + if (Threads.stop) + return VALUE_DRAW; + */ + + // Step 20. Check for mate and stalemate + // All legal moves have been searched and if there are no legal moves, it + // must be a mate or a stalemate. If we are in a singular extension search then + // return a fail low score. + + assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); + + if (!moveCount) + bestValue = excludedMove ? alpha + : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; + + else if (bestMove) + update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, + quietsSearched, quietCount, capturesSearched, captureCount, depth); + + // Bonus for prior countermove that caused the fail low + else if ( (depth >= 3 || PvNode) + && !priorCapture) + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth)); + + if (PvNode) + bestValue = std::min(bestValue, maxValue); + + // If no good move is found and the previous position was ttPv, then the previous + // opponent move is probably good and the new position is added to the search tree. + if (bestValue <= alpha) + ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3); + // Otherwise, a counter move has been found and if the position is the last leaf + // in the search tree, remove the position from the search tree. + else if (depth > 3) + ss->ttPv = ss->ttPv && (ss+1)->ttPv; + + if (!excludedMove && !(rootNode && thisThread->pvIdx)) + tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, + bestValue >= beta ? BOUND_LOWER : + PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, + depth, bestMove, ss->staticEval); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + + // qsearch() is the quiescence search function, which is called by the main search + // function with zero depth, or recursively with further decreasing depth per call. + template + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { + + constexpr bool PvNode = NT == PV; + + assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); + assert(PvNode || (alpha == beta - 1)); + assert(depth <= 0); + + Move pv[MAX_PLY+1]; + StateInfo st; + TTEntry* tte; + Key posKey; + Move ttMove, move, bestMove; + Depth ttDepth; + Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; + bool pvHit, givesCheck, captureOrPromotion; + int moveCount; + + if (PvNode) + { + oldAlpha = alpha; // To flag BOUND_EXACT when eval above alpha and no available moves + (ss+1)->pv = pv; + ss->pv[0] = MOVE_NONE; + } + + Thread* thisThread = pos.this_thread(); + (ss+1)->ply = ss->ply + 1; + bestMove = MOVE_NONE; + ss->inCheck = pos.checkers(); + moveCount = 0; + + // Check for an immediate draw or maximum ply reached + if ( pos.is_draw(ss->ply) + || ss->ply >= MAX_PLY) + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; + + assert(0 <= ss->ply && ss->ply < MAX_PLY); + + // Decide whether or not to include checks: this fixes also the type of + // TT entry depth that we are going to use. Note that in qsearch we use + // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. + ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS + : DEPTH_QS_NO_CHECKS; + // Transposition table lookup + posKey = pos.key(); + tte = TT.probe(posKey, ss->ttHit); + ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; + ttMove = ss->ttHit ? tte->move() : MOVE_NONE; + pvHit = ss->ttHit && tte->is_pv(); + + if ( !PvNode + && ss->ttHit + && tte->depth() >= ttDepth + && ttValue != VALUE_NONE // Only in case of TT access race + && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) + : (tte->bound() & BOUND_UPPER))) + return ttValue; + + // Evaluate the position statically + if (ss->inCheck) + { + ss->staticEval = VALUE_NONE; + bestValue = futilityBase = -VALUE_INFINITE; + } + else + { + if (ss->ttHit) + { + // Never assume anything about values stored in TT + if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) + ss->staticEval = bestValue = evaluate(pos); + + // Can ttValue be used as a better position evaluation? + if ( ttValue != VALUE_NONE + && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) + bestValue = ttValue; + } + else + ss->staticEval = bestValue = + (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) + : -(ss-1)->staticEval + 2 * Tempo; + + // Stand pat. Return immediately if static value is at least beta + if (bestValue >= beta) + { + if (!ss->ttHit) + tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, + DEPTH_NONE, MOVE_NONE, ss->staticEval); + + return bestValue; + } + + if (PvNode && bestValue > alpha) + alpha = bestValue; + + futilityBase = bestValue + 145; + } + + const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, + nullptr , (ss-4)->continuationHistory, + nullptr , (ss-6)->continuationHistory }; + + // Initialize a MovePicker object for the current position, and prepare + // to search the moves. Because the depth is <= 0 here, only captures, + // queen and checking knight promotions, and other checks(only if depth >= DEPTH_QS_CHECKS) + // will be generated. + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, + &thisThread->captureHistory, + contHist, + to_sq((ss-1)->currentMove)); + + // Loop through the moves until no moves remain or a beta cutoff occurs + while ((move = mp.next_move()) != MOVE_NONE) + { + assert(is_ok(move)); + + givesCheck = pos.gives_check(move); + captureOrPromotion = pos.capture_or_promotion(move); + + moveCount++; + + // Futility pruning + if ( !ss->inCheck + && !givesCheck + && futilityBase > -VALUE_KNOWN_WIN + && !pos.advanced_pawn_push(move)) + { + assert(type_of(move) != ENPASSANT); // Due to !pos.advanced_pawn_push + + // moveCount pruning + if (moveCount > 2) + continue; + + futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))]; + + if (futilityValue <= alpha) + { + bestValue = std::max(bestValue, futilityValue); + continue; + } + + if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + { + bestValue = std::max(bestValue, futilityBase); + continue; + } + } + + // Do not search moves with negative SEE values + if ( !ss->inCheck + && !(givesCheck && pos.is_discovery_check_on_king(~pos.side_to_move(), move)) + && !pos.see_ge(move)) + continue; + + // Speculative prefetch as early as possible + prefetch(TT.first_entry(pos.key_after(move))); + + // Check for legality just before making the move + if (!pos.legal(move)) + { + moveCount--; + continue; + } + + ss->currentMove = move; + ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] + [captureOrPromotion] + [pos.moved_piece(move)] + [to_sq(move)]; + + if ( !captureOrPromotion + && moveCount + && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold + && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold) + continue; + + // Make and search the move + pos.do_move(move, st, givesCheck); + value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); + pos.undo_move(move); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // Check for a new best move + if (value > bestValue) + { + bestValue = value; + + if (value > alpha) + { + bestMove = move; + + if (PvNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss+1)->pv); + + if (PvNode && value < beta) // Update alpha here! + alpha = value; + else + break; // Fail high + } + } + } + + // All legal moves have been searched. A special case: if we're in check + // and no legal moves were found, it is checkmate. + if (ss->inCheck && bestValue == -VALUE_INFINITE) + return mated_in(ss->ply); // Plies to mate from the root + + tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, + bestValue >= beta ? BOUND_LOWER : + PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, + ttDepth, bestMove, ss->staticEval); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + + // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" to + // "plies to mate from the current position". Standard scores are unchanged. + // The function is called before storing a value in the transposition table. + + Value value_to_tt(Value v, int ply) { + + assert(v != VALUE_NONE); + + return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply + : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; + } + + + // value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score + // from the transposition table (which refers to the plies to mate/be mated from + // current position) to "plies to mate/be mated (TB win/loss) from the root". However, + // for mate scores, to avoid potentially false mate scores related to the 50 moves rule + // and the graph history interaction, we return an optimal TB score instead. + + Value value_from_tt(Value v, int ply, int r50c) { + + if (v == VALUE_NONE) + return VALUE_NONE; + + if (v >= VALUE_TB_WIN_IN_MAX_PLY) // TB win or better + { + if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c) + return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score + + return v - ply; + } + + if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse + { + if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c) + return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score + + return v + ply; + } + + return v; + } + + + // update_pv() adds current move and appends child pv[] + + void update_pv(Move* pv, Move move, Move* childPv) { + + for (*pv++ = move; childPv && *childPv != MOVE_NONE; ) + *pv++ = *childPv++; + *pv = MOVE_NONE; + } + + + // update_all_stats() updates stats at the end of search() when a bestMove is found + + void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, + Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth) { + + int bonus1, bonus2; + Color us = pos.side_to_move(); + Thread* thisThread = pos.this_thread(); + CapturePieceToHistory& captureHistory = thisThread->captureHistory; + Piece moved_piece = pos.moved_piece(bestMove); + PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); + + bonus1 = stat_bonus(depth + 1); + bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus + : stat_bonus(depth); // smaller bonus + + if (!pos.capture_or_promotion(bestMove)) + { + update_quiet_stats(pos, ss, bestMove, bonus2, depth); + + // Decrease all the non-best quiet moves + for (int i = 0; i < quietCount; ++i) + { + thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; + update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2); + } + } + else + captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; + + // Extra penalty for a quiet early move that was not a TT move or main killer move in previous ply when it gets refuted + if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) + && !pos.captured_piece()) + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); + + // Decrease all the non-best capture moves + for (int i = 0; i < captureCount; ++i) + { + moved_piece = pos.moved_piece(capturesSearched[i]); + captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); + captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1; + } + } + + + // update_continuation_histories() updates histories of the move pairs formed + // by moves at ply -1, -2, -4, and -6 with current move. + + void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { + + for (int i : {1, 2, 4, 6}) + { + if (ss->inCheck && i > 2) + break; + if (is_ok((ss-i)->currentMove)) + (*(ss-i)->continuationHistory)[pc][to] << bonus; + } + } + + + // update_quiet_stats() updates move sorting heuristics + + void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus, int depth) { + + if (ss->killers[0] != move) + { + ss->killers[1] = ss->killers[0]; + ss->killers[0] = move; + } + + Color us = pos.side_to_move(); + Thread* thisThread = pos.this_thread(); + thisThread->mainHistory[us][from_to(move)] << bonus; + update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); + + if (type_of(pos.moved_piece(move)) != PAWN) + thisThread->mainHistory[us][from_to(reverse_move(move))] << -bonus; + + if (is_ok((ss-1)->currentMove)) + { + Square prevSq = to_sq((ss-1)->currentMove); + thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; + } + + if (depth > 11 && ss->ply < MAX_LPH) + thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 7); + } + + // When playing with strength handicap, choose best move among a set of RootMoves + // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. + + Move Skill::pick_best(size_t multiPV) { + + const RootMoves& rootMoves = Threads.main()->rootMoves; + static PRNG rng(now()); // PRNG sequence should be non-deterministic + + // RootMoves are already sorted by score in descending order + Value topScore = rootMoves[0].score; + int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg); + int weakness = 120 - 2 * level; + int maxScore = -VALUE_INFINITE; + + // Choose best move. For each move score we add two terms, both dependent on + // weakness. One is deterministic and bigger for weaker levels, and one is + // random. Then we choose the move with the resulting highest score. + for (size_t i = 0; i < multiPV; ++i) + { + // This is our magic formula + int push = ( weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % weakness)) / 128; + + if (rootMoves[i].score + push >= maxScore) + { + maxScore = rootMoves[i].score + push; + best = rootMoves[i].pv[0]; + } + } + + return best; + } + +} // namespace + + +/// MainThread::check_time() is used to print debug info and, more importantly, +/// to detect when we are out of available time and thus stop the search. + +void MainThread::check_time() { + + if (--callsCnt > 0) + return; + + // When using nodes, ensure checking rate is not lower than 0.1% of nodes + callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024; + + static TimePoint lastInfoTime = now(); + + TimePoint elapsed = Time.elapsed(); + TimePoint tick = Limits.startTime + elapsed; + + if (tick - lastInfoTime >= 1000) + { + lastInfoTime = tick; + dbg_print(); + } + + // We should not stop pondering until told so by the GUI + if (ponder) + return; + + if ( (Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit)) + || (Limits.movetime && elapsed >= Limits.movetime) + || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) + Threads.stop = true; +} + + +/// UCI::pv() formats PV information according to the UCI protocol. UCI requires +/// that all (if any) unsearched PV lines are sent using a previous search score. + +string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { + + std::stringstream ss; + TimePoint elapsed = Time.elapsed() + 1; + const RootMoves& rootMoves = pos.this_thread()->rootMoves; + size_t pvIdx = pos.this_thread()->pvIdx; + size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); + uint64_t nodesSearched = Threads.nodes_searched(); + uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); + + for (size_t i = 0; i < multiPV; ++i) + { + bool updated = rootMoves[i].score != -VALUE_INFINITE; + + if (depth == 1 && !updated && i > 0) + continue; + + Depth d = updated ? depth : std::max(1, depth - 1); + Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore; + + if (v == -VALUE_INFINITE) + v = VALUE_ZERO; + + bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; + v = tb ? rootMoves[i].tbScore : v; + + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; + + ss << "info" + << " depth " << d + << " seldepth " << rootMoves[i].selDepth + << " multipv " << i + 1 + << " score " << UCI::value(v); + + if (Options["UCI_ShowWDL"]) + ss << UCI::wdl(v, pos.game_ply()); + + if (!tb && i == pvIdx) + ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); + + ss << " nodes " << nodesSearched + << " nps " << nodesSearched * 1000 / elapsed; + + if (elapsed > 1000) // Earlier makes little sense + ss << " hashfull " << TT.hashfull(); + + if (Eval::nnueHits && Eval::evalHits) { + ss << " nnuehits " << static_cast(Eval::nnueHits * 1000 / Eval::evalHits); + } + + ss << " tbhits " << tbHits + << " time " << elapsed + << " pv"; + + for (Move m : rootMoves[i].pv) + ss << " " << UCI::move(m, pos.is_chess960()); + } + + return ss.str(); +} + + +/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move +/// before exiting the search, for instance, in case we stop the search during a +/// fail high at root. We try hard to have a ponder move to return to the GUI, +/// otherwise in case of 'ponder on' we have nothing to think on. + +bool RootMove::extract_ponder_from_tt(Position& pos) { + + StateInfo st; + bool ttHit; + + assert(pv.size() == 1); + + if (pv[0] == MOVE_NONE) + return false; + + pos.do_move(pv[0], st); + TTEntry* tte = TT.probe(pos.key(), ttHit); + + if (ttHit) + { + Move m = tte->move(); // Local copy to be SMP safe + if (MoveList(pos).contains(m)) + pv.push_back(m); + } + + pos.undo_move(pv[0]); + return pv.size() > 1; +} + +void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { + + RootInTB = false; + UseRule50 = bool(Options["Syzygy50MoveRule"]); + ProbeDepth = int(Options["SyzygyProbeDepth"]); + Cardinality = int(Options["SyzygyProbeLimit"]); + bool dtz_available = true; + + // Tables with fewer pieces than SyzygyProbeLimit are searched with + // ProbeDepth == DEPTH_ZERO + if (Cardinality > MaxCardinality) + { + Cardinality = MaxCardinality; + ProbeDepth = 0; + } + + if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + // Rank moves using DTZ tables + RootInTB = root_probe(pos, rootMoves); + + if (!RootInTB) + { + // DTZ tables are missing; try to rank moves using WDL tables + dtz_available = false; + RootInTB = root_probe_wdl(pos, rootMoves); + } + } + + if (RootInTB) + { + // Sort moves according to TB rank + std::stable_sort(rootMoves.begin(), rootMoves.end(), + [](const RootMove &a, const RootMove &b) { return a.tbRank > b.tbRank; } ); + + // Probe during search only if DTZ is not available and we are winning + if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) + Cardinality = 0; + } + else + { + // Clean up if root_probe() and root_probe_wdl() have failed + for (auto& m : rootMoves) + m.tbRank = 0; + } +} diff --git a/engines/stockfish13/search.h b/engines/stockfish13/search.h new file mode 100644 index 0000000..f60da4a --- /dev/null +++ b/engines/stockfish13/search.h @@ -0,0 +1,110 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SEARCH_H_INCLUDED +#define SEARCH_H_INCLUDED + +#include + +#include "misc.h" +#include "movepick.h" +#include "types.h" + +class Position; + +namespace Search { + +/// Threshold used for countermoves based pruning +constexpr int CounterMovePruneThreshold = 0; + + +/// Stack struct keeps track of the information we need to remember from nodes +/// shallower and deeper in the tree during the search. Each search thread has +/// its own array of Stack objects, indexed by the current ply. + +struct Stack { + Move* pv; + PieceToHistory* continuationHistory; + int ply; + Move currentMove; + Move excludedMove; + Move killers[2]; + Value staticEval; + int statScore; + int moveCount; + bool inCheck; + bool ttPv; + bool ttHit; +}; + + +/// RootMove struct is used for moves at the root of the tree. For each root move +/// we store a score and a PV (really a refutation in the case of moves which +/// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. + +struct RootMove { + + explicit RootMove(Move m) : pv(1, m) {} + bool extract_ponder_from_tt(Position& pos); + bool operator==(const Move& m) const { return pv[0] == m; } + bool operator<(const RootMove& m) const { // Sort in descending order + return m.score != score ? m.score < score + : m.previousScore < previousScore; + } + + Value score = -VALUE_INFINITE; + Value previousScore = -VALUE_INFINITE; + int selDepth = 0; + int tbRank = 0; + int bestMoveCount = 0; + Value tbScore; + std::vector pv; +}; + +typedef std::vector RootMoves; + + +/// LimitsType struct stores information sent by GUI about available time to +/// search the current move, maximum depth/time, or if we are in analysis mode. + +struct LimitsType { + + LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC + time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); + movestogo = depth = mate = perft = infinite = 0; + nodes = 0; + } + + bool use_time_management() const { + return time[WHITE] || time[BLACK]; + } + + std::vector searchmoves; + TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; + int movestogo, depth, mate, perft, infinite; + int64_t nodes; +}; + +extern LimitsType Limits; + +void init(); +void clear(); + +} // namespace Search + +#endif // #ifndef SEARCH_H_INCLUDED diff --git a/engines/stockfish13/syzygy/tbprobe.cpp b/engines/stockfish13/syzygy/tbprobe.cpp new file mode 100644 index 0000000..4d682f1 --- /dev/null +++ b/engines/stockfish13/syzygy/tbprobe.cpp @@ -0,0 +1,1612 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include // For std::memset and std::memcpy +#include +#include +#include +#include +#include +#include +#include + +#include "../bitboard.h" +#include "../movegen.h" +#include "../position.h" +#include "../search.h" +#include "../types.h" +#include "../uci.h" + +#include "tbprobe.h" + +#ifndef _WIN32 +#include +#include +#include +#include +#else +#define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +# define NOMINMAX // Disable macros min() and max() +#endif +#include +#endif + +using namespace Tablebases; + +int Tablebases::MaxCardinality; + +namespace { + +constexpr int TBPIECES = 7; // Max number of supported pieces + +enum { BigEndian, LittleEndian }; +enum TBType { WDL, DTZ }; // Used as template parameter + +// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables +enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 }; + +inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); } +inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } + +const std::string PieceToChar = " PNBRQK pnbrqk"; + +int MapPawns[SQUARE_NB]; +int MapB1H1H7[SQUARE_NB]; +int MapA1D1D4[SQUARE_NB]; +int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] + +int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements +int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] +int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D] + +// Comparison function to sort leading pawns in ascending MapPawns[] order +bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; } +int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } + +constexpr Value WDL_to_value[] = { + -VALUE_MATE + MAX_PLY + 1, + VALUE_DRAW - 2, + VALUE_DRAW, + VALUE_DRAW + 2, + VALUE_MATE - MAX_PLY - 1 +}; + +template +inline void swap_endian(T& x) +{ + static_assert(std::is_unsigned::value, "Argument of swap_endian not unsigned"); + + uint8_t tmp, *c = (uint8_t*)&x; + for (int i = 0; i < Half; ++i) + tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; +} +template<> inline void swap_endian(uint8_t&) {} + +template T number(void* addr) +{ + static const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; + static const bool IsLittleEndian = (Le.c[0] == 4); + + T v; + + if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) + std::memcpy(&v, addr, sizeof(T)); + else + v = *((T*)addr); + + if (LE != IsLittleEndian) + swap_endian(v); + return v; +} + +// DTZ tables don't store valid scores for moves that reset the rule50 counter +// like captures and pawn moves but we can easily recover the correct dtz of the +// previous move if we know the position's WDL score. +int dtz_before_zeroing(WDLScore wdl) { + return wdl == WDLWin ? 1 : + wdl == WDLCursedWin ? 101 : + wdl == WDLBlessedLoss ? -101 : + wdl == WDLLoss ? -1 : 0; +} + +// Return the sign of a number (-1, 0, 1) +template int sign_of(T val) { + return (T(0) < val) - (val < T(0)); +} + +// Numbers in little endian used by sparseIndex[] to point into blockLength[] +struct SparseEntry { + char block[4]; // Number of block + char offset[2]; // Offset within the block +}; + +static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); + +typedef uint16_t Sym; // Huffman symbol + +struct LR { + enum Side { Left, Right }; + + uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 + // bits is the right-hand symbol. If symbol has length 1, + // then the left-hand symbol is the stored value. + template + Sym get() { + return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] : + S == Right ? (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1)); + } +}; + +static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes"); + +// Tablebases data layout is structured as following: +// +// TBFile: memory maps/unmaps the physical .rtbw and .rtbz files +// TBTable: one object for each file with corresponding indexing information +// TBTables: has ownership of TBTable objects, keeping a list and a hash + +// class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are +// memory mapped for best performance. Files are mapped at first access: at init +// time only existence of the file is checked. +class TBFile : public std::ifstream { + + std::string fname; + +public: + // Look for and open the file among the Paths directories where the .rtbw + // and .rtbz files can be found. Multiple directories are separated by ";" + // on Windows and by ":" on Unix-based operating systems. + // + // Example: + // C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6 + static std::string Paths; + + TBFile(const std::string& f) { + +#ifndef _WIN32 + constexpr char SepChar = ':'; +#else + constexpr char SepChar = ';'; +#endif + std::stringstream ss(Paths); + std::string path; + + while (std::getline(ss, path, SepChar)) { + fname = path + "/" + f; + std::ifstream::open(fname); + if (is_open()) + return; + } + } + + // Memory map the file and check it. File should be already open and will be + // closed after mapping. + uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) { + + assert(is_open()); + + close(); // Need to re-open to get native file descriptor + +#ifndef _WIN32 + struct stat statbuf; + int fd = ::open(fname.c_str(), O_RDONLY); + + if (fd == -1) + return *baseAddress = nullptr, nullptr; + + fstat(fd, &statbuf); + + if (statbuf.st_size % 64 != 16) + { + std::cerr << "Corrupt tablebase file " << fname << std::endl; + exit(EXIT_FAILURE); + } + + *mapping = statbuf.st_size; + *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); +#if defined(MADV_RANDOM) + madvise(*baseAddress, statbuf.st_size, MADV_RANDOM); +#endif + ::close(fd); + + if (*baseAddress == MAP_FAILED) + { + std::cerr << "Could not mmap() " << fname << std::endl; + exit(EXIT_FAILURE); + } +#else + // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored. + HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); + + if (fd == INVALID_HANDLE_VALUE) + return *baseAddress = nullptr, nullptr; + + DWORD size_high; + DWORD size_low = GetFileSize(fd, &size_high); + + if (size_low % 64 != 16) + { + std::cerr << "Corrupt tablebase file " << fname << std::endl; + exit(EXIT_FAILURE); + } + + HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr); + CloseHandle(fd); + + if (!mmap) + { + std::cerr << "CreateFileMapping() failed" << std::endl; + exit(EXIT_FAILURE); + } + + *mapping = (uint64_t)mmap; + *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); + + if (!*baseAddress) + { + std::cerr << "MapViewOfFile() failed, name = " << fname + << ", error = " << GetLastError() << std::endl; + exit(EXIT_FAILURE); + } +#endif + uint8_t* data = (uint8_t*)*baseAddress; + + constexpr uint8_t Magics[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 }, + { 0x71, 0xE8, 0x23, 0x5D } }; + + if (memcmp(data, Magics[type == WDL], 4)) + { + std::cerr << "Corrupted table in file " << fname << std::endl; + unmap(*baseAddress, *mapping); + return *baseAddress = nullptr, nullptr; + } + + return data + 4; // Skip Magics's header + } + + static void unmap(void* baseAddress, uint64_t mapping) { + +#ifndef _WIN32 + munmap(baseAddress, mapping); +#else + UnmapViewOfFile(baseAddress); + CloseHandle((HANDLE)mapping); +#endif + } +}; + +std::string TBFile::Paths; + +// struct PairsData contains low level indexing information to access TB data. +// There are 8, 4 or 2 PairsData records for each TBTable, according to type of +// table and if positions have pawns or not. It is populated at first access. +struct PairsData { + uint8_t flags; // Table flags, see enum TBFlag + uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols + uint8_t minSymLen; // Minimum length in bits of the Huffman symbols + uint32_t blocksNum; // Number of blocks in the TB file + size_t sizeofBlock; // Block size in bytes + size_t span; // About every span values there is a SparseIndex[] entry + Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value + LR* btree; // btree[sym] stores the left and right symbols that expand sym + uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 + uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum + SparseEntry* sparseIndex; // Partial indices into blockLength[] + size_t sparseIndexSize; // Size of SparseIndex[] table + uint8_t* data; // Start of Huffman compressed data + std::vector base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l + std::vector symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 + Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups + uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces + int groupLen[TBPIECES+1]; // Number of pieces in a given group: KRKN -> (3, 1) + uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ) +}; + +// struct TBTable contains indexing information to access the corresponding TBFile. +// There are 2 types of TBTable, corresponding to a WDL or a DTZ file. TBTable +// is populated at init time but the nested PairsData records are populated at +// first access, when the corresponding file is memory mapped. +template +struct TBTable { + typedef typename std::conditional::type Ret; + + static constexpr int Sides = Type == WDL ? 2 : 1; + + std::atomic_bool ready; + void* baseAddress; + uint8_t* map; + uint64_t mapping; + Key key; + Key key2; + int pieceCount; + bool hasPawns; + bool hasUniquePieces; + uint8_t pawnCount[2]; // [Lead color / other color] + PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0] + + PairsData* get(int stm, int f) { + return &items[stm % Sides][hasPawns ? f : 0]; + } + + TBTable() : ready(false), baseAddress(nullptr) {} + explicit TBTable(const std::string& code); + explicit TBTable(const TBTable& wdl); + + ~TBTable() { + if (baseAddress) + TBFile::unmap(baseAddress, mapping); + } +}; + +template<> +TBTable::TBTable(const std::string& code) : TBTable() { + + StateInfo st; + Position pos; + + key = pos.set(code, WHITE, &st).material_key(); + pieceCount = pos.count(); + hasPawns = pos.pieces(PAWN); + + hasUniquePieces = false; + for (Color c : { WHITE, BLACK }) + for (PieceType pt = PAWN; pt < KING; ++pt) + if (popcount(pos.pieces(c, pt)) == 1) + hasUniquePieces = true; + + // Set the leading color. In case both sides have pawns the leading color + // is the side with less pawns because this leads to better compression. + bool c = !pos.count(BLACK) + || ( pos.count(WHITE) + && pos.count(BLACK) >= pos.count(WHITE)); + + pawnCount[0] = pos.count(c ? WHITE : BLACK); + pawnCount[1] = pos.count(c ? BLACK : WHITE); + + key2 = pos.set(code, BLACK, &st).material_key(); +} + +template<> +TBTable::TBTable(const TBTable& wdl) : TBTable() { + + // Use the corresponding WDL table to avoid recalculating all from scratch + key = wdl.key; + key2 = wdl.key2; + pieceCount = wdl.pieceCount; + hasPawns = wdl.hasPawns; + hasUniquePieces = wdl.hasUniquePieces; + pawnCount[0] = wdl.pawnCount[0]; + pawnCount[1] = wdl.pawnCount[1]; +} + +// class TBTables creates and keeps ownership of the TBTable objects, one for +// each TB file found. It supports a fast, hash based, table lookup. Populated +// at init time, accessed at probe time. +class TBTables { + + struct Entry + { + Key key; + TBTable* wdl; + TBTable* dtz; + + template + TBTable* get() const { + return (TBTable*)(Type == WDL ? (void*)wdl : (void*)dtz); + } + }; + + static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb + static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket + + Entry hashTable[Size + Overflow]; + + std::deque> wdlTable; + std::deque> dtzTable; + + void insert(Key key, TBTable* wdl, TBTable* dtz) { + uint32_t homeBucket = (uint32_t)key & (Size - 1); + Entry entry{ key, wdl, dtz }; + + // Ensure last element is empty to avoid overflow when looking up + for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) { + Key otherKey = hashTable[bucket].key; + if (otherKey == key || !hashTable[bucket].get()) { + hashTable[bucket] = entry; + return; + } + + // Robin Hood hashing: If we've probed for longer than this element, + // insert here and search for a new spot for the other element instead. + uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1); + if (otherHomeBucket > homeBucket) { + std::swap(entry, hashTable[bucket]); + key = otherKey; + homeBucket = otherHomeBucket; + } + } + std::cerr << "TB hash table size too low!" << std::endl; + exit(EXIT_FAILURE); + } + +public: + template + TBTable* get(Key key) { + for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) { + if (entry->key == key || !entry->get()) + return entry->get(); + } + } + + void clear() { + memset(hashTable, 0, sizeof(hashTable)); + wdlTable.clear(); + dtzTable.clear(); + } + size_t size() const { return wdlTable.size(); } + void add(const std::vector& pieces); +}; + +TBTables TBTables; + +// If the corresponding file exists two new objects TBTable and TBTable +// are created and added to the lists and hash table. Called at init time. +void TBTables::add(const std::vector& pieces) { + + std::string code; + + for (PieceType pt : pieces) + code += PieceToChar[pt]; + + TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK + + if (!file.is_open()) // Only WDL file is checked + return; + + file.close(); + + MaxCardinality = std::max((int)pieces.size(), MaxCardinality); + + wdlTable.emplace_back(code); + dtzTable.emplace_back(wdlTable.back()); + + // Insert into the hash keys for both colors: KRvK with KR white and black + insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back()); + insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back()); +} + +// TB tables are compressed with canonical Huffman code. The compressed data is divided into +// blocks of size d->sizeofBlock, and each block stores a variable number of symbols. +// Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols +// (recursively). If you keep expanding the symbols in a block, you end up with up to 65536 +// WDL or DTZ values. Each symbol represents up to 256 values and will correspond after +// Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most +// 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly +// of draws or mostly of wins, but such tables are actually quite common. In principle, the +// blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for +// mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so +// in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long. +// The generator picks the size that leads to the smallest table. The "book" of symbols and +// Huffman codes is the same for all blocks in the table. A non-symmetric pawnless TB file +// will have one table for wtm and one for btm, a TB file with pawns will have tables per +// file a,b,c,d also in this case one set for wtm and one for btm. +int decompress_pairs(PairsData* d, uint64_t idx) { + + // Special case where all table positions store the same value + if (d->flags & TBFlag::SingleValue) + return d->minSymLen; + + // First we need to locate the right block that stores the value at index "idx". + // Because each block n stores blockLength[n] + 1 values, the index i of the block + // that contains the value at position idx is: + // + // for (i = -1, sum = 0; sum <= idx; i++) + // sum += blockLength[i + 1] + 1; + // + // This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that + // point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry + // that stores the blockLength[] index and the offset within that block of the value + // with index I(k), where: + // + // I(k) = k * d->span + d->span / 2 (1) + + // First step is to get the 'k' of the I(k) nearest to our idx, using definition (1) + uint32_t k = uint32_t(idx / d->span); + + // Then we read the corresponding SparseIndex[] entry + uint32_t block = number(&d->sparseIndex[k].block); + int offset = number(&d->sparseIndex[k].offset); + + // Now compute the difference idx - I(k). From definition of k we know that + // + // idx = k * d->span + idx % d->span (2) + // + // So from (1) and (2) we can compute idx - I(K): + int diff = idx % d->span - d->span / 2; + + // Sum the above to offset to find the offset corresponding to our idx + offset += diff; + + // Move to previous/next block, until we reach the correct block that contains idx, + // that is when 0 <= offset <= d->blockLength[block] + while (offset < 0) + offset += d->blockLength[--block] + 1; + + while (offset > d->blockLength[block]) + offset -= d->blockLength[block++] + 1; + + // Finally, we find the start address of our block of canonical Huffman symbols + uint32_t* ptr = (uint32_t*)(d->data + ((uint64_t)block * d->sizeofBlock)); + + // Read the first 64 bits in our block, this is a (truncated) sequence of + // unknown number of symbols of unknown length but we know the first one + // is at the beginning of this 64 bits sequence. + uint64_t buf64 = number(ptr); ptr += 2; + int buf64Size = 64; + Sym sym; + + while (true) { + int len = 0; // This is the symbol length - d->min_sym_len + + // Now get the symbol length. For any symbol s64 of length l right-padded + // to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we + // can find the symbol length iterating through base64[]. + while (buf64 < d->base64[len]) + ++len; + + // All the symbols of a given length are consecutive integers (numerical + // sequence property), so we can compute the offset of our symbol of + // length len, stored at the beginning of buf64. + sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen)); + + // Now add the value of the lowest symbol of length len to get our symbol + sym += number(&d->lowestSym[len]); + + // If our offset is within the number of values represented by symbol sym + // we are done... + if (offset < d->symlen[sym] + 1) + break; + + // ...otherwise update the offset and continue to iterate + offset -= d->symlen[sym] + 1; + len += d->minSymLen; // Get the real length + buf64 <<= len; // Consume the just processed symbol + buf64Size -= len; + + if (buf64Size <= 32) { // Refill the buffer + buf64Size += 32; + buf64 |= (uint64_t)number(ptr++) << (64 - buf64Size); + } + } + + // Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols. + // We binary-search for our value recursively expanding into the left and + // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 + // that will store the value we need. + while (d->symlen[sym]) { + + Sym left = d->btree[sym].get(); + + // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and + // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then + // we know that, for instance the ten-th value (offset = 10) will be on + // the left side because in Recursive Pairing child symbols are adjacent. + if (offset < d->symlen[left] + 1) + sym = left; + else { + offset -= d->symlen[left] + 1; + sym = d->btree[sym].get(); + } + } + + return d->btree[sym].get(); +} + +bool check_dtz_stm(TBTable*, int, File) { return true; } + +bool check_dtz_stm(TBTable* entry, int stm, File f) { + + auto flags = entry->get(stm, f)->flags; + return (flags & TBFlag::STM) == stm + || ((entry->key == entry->key2) && !entry->hasPawns); +} + +// DTZ scores are sorted by frequency of occurrence and then assigned the +// values 0, 1, 2, ... in order of decreasing frequency. This is done for each +// of the four WDLScore values. The mapping information necessary to reconstruct +// the original values is stored in the TB file and read during map[] init. +WDLScore map_score(TBTable*, File, int value, WDLScore) { return WDLScore(value - 2); } + +int map_score(TBTable* entry, File f, int value, WDLScore wdl) { + + constexpr int WDLMap[] = { 1, 3, 0, 2, 0 }; + + auto flags = entry->get(0, f)->flags; + + uint8_t* map = entry->map; + uint16_t* idx = entry->get(0, f)->map_idx; + if (flags & TBFlag::Mapped) { + if (flags & TBFlag::Wide) + value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value]; + else + value = map[idx[WDLMap[wdl + 2]] + value]; + } + + // DTZ tables store distance to zero in number of moves or plies. We + // want to return plies, so we have convert to plies when needed. + if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies)) + || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) + || wdl == WDLCursedWin + || wdl == WDLBlessedLoss) + value *= 2; + + return value + 1; +} + +// Compute a unique index out of a position and use it to probe the TB file. To +// encode k pieces of same type and color, first sort the pieces by square in +// ascending order s1 <= s2 <= ... <= sk then compute the unique index as: +// +// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] +// +template +Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { + + Square squares[TBPIECES]; + Piece pieces[TBPIECES]; + uint64_t idx; + int next = 0, size = 0, leadPawnsCnt = 0; + PairsData* d; + Bitboard b, leadPawns = 0; + File tbFile = FILE_A; + + // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. + // If both sides have the same pieces keys are equal. In this case TB tables + // only store the 'white to move' case, so if the position to lookup has black + // to move, we need to switch the color and flip the squares before to lookup. + bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move()); + + // TB files are calculated for white as stronger side. For instance we have + // KRvK, not KvKR. A position where stronger side is white will have its + // material key == entry->key, otherwise we have to switch the color and + // flip the squares before to lookup. + bool blackStronger = (pos.material_key() != entry->key); + + int flipColor = (symmetricBlackToMove || blackStronger) * 8; + int flipSquares = (symmetricBlackToMove || blackStronger) * 56; + int stm = (symmetricBlackToMove || blackStronger) ^ pos.side_to_move(); + + // For pawns, TB files store 4 separate tables according if leading pawn is on + // file a, b, c or d after reordering. The leading pawn is the one with maximum + // MapPawns[] value, that is the one most toward the edges and with lowest rank. + if (entry->hasPawns) { + + // In all the 4 tables, pawns are at the beginning of the piece sequence and + // their color is the reference one. So we just pick the first one. + Piece pc = Piece(entry->get(0, 0)->pieces[0] ^ flipColor); + + assert(type_of(pc) == PAWN); + + leadPawns = b = pos.pieces(color_of(pc), PAWN); + do + squares[size++] = pop_lsb(&b) ^ flipSquares; + while (b); + + leadPawnsCnt = size; + + std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp)); + + tbFile = File(edge_distance(file_of(squares[0]))); + } + + // DTZ tables are one-sided, i.e. they store positions only for white to + // move or only for black to move, so check for side to move to be stm, + // early exit otherwise. + if (!check_dtz_stm(entry, stm, tbFile)) + return *result = CHANGE_STM, Ret(); + + // Now we are ready to get all the position pieces (but the lead pawns) and + // directly map them to the correct color and square. + b = pos.pieces() ^ leadPawns; + do { + Square s = pop_lsb(&b); + squares[size] = s ^ flipSquares; + pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); + } while (b); + + assert(size >= 2); + + d = entry->get(stm, tbFile); + + // Then we reorder the pieces to have the same sequence as the one stored + // in pieces[i]: the sequence that ensures the best compression. + for (int i = leadPawnsCnt; i < size - 1; ++i) + for (int j = i + 1; j < size; ++j) + if (d->pieces[i] == pieces[j]) + { + std::swap(pieces[i], pieces[j]); + std::swap(squares[i], squares[j]); + break; + } + + // Now we map again the squares so that the square of the lead piece is in + // the triangle A1-D1-D4. + if (file_of(squares[0]) > FILE_D) + for (int i = 0; i < size; ++i) + squares[i] = flip_file(squares[i]); + + // Encode leading pawns starting with the one with minimum MapPawns[] and + // proceeding in ascending order. + if (entry->hasPawns) { + idx = LeadPawnIdx[leadPawnsCnt][squares[0]]; + + std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp); + + for (int i = 1; i < leadPawnsCnt; ++i) + idx += Binomial[i][MapPawns[squares[i]]]; + + goto encode_remaining; // With pawns we have finished special treatments + } + + // In positions withouth pawns, we further flip the squares to ensure leading + // piece is below RANK_5. + if (rank_of(squares[0]) > RANK_4) + for (int i = 0; i < size; ++i) + squares[i] = flip_rank(squares[i]); + + // Look for the first piece of the leading group not on the A1-D4 diagonal + // and ensure it is mapped below the diagonal. + for (int i = 0; i < d->groupLen[0]; ++i) { + if (!off_A1H8(squares[i])) + continue; + + if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 + for (int j = i; j < size; ++j) + squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63); + break; + } + + // Encode the leading group. + // + // Suppose we have KRvK. Let's say the pieces are on square numbers wK, wR + // and bK (each 0...63). The simplest way to map this position to an index + // is like this: + // + // index = wK * 64 * 64 + wR * 64 + bK; + // + // But this way the TB is going to have 64*64*64 = 262144 positions, with + // lots of positions being equivalent (because they are mirrors of each + // other) and lots of positions being invalid (two pieces on one square, + // adjacent kings, etc.). + // Usually the first step is to take the wK and bK together. There are just + // 462 ways legal and not-mirrored ways to place the wK and bK on the board. + // Once we have placed the wK and bK, there are 62 squares left for the wR + // Mapping its square from 0..63 to available squares 0..61 can be done like: + // + // wR -= (wR > wK) + (wR > bK); + // + // In words: if wR "comes later" than wK, we deduct 1, and the same if wR + // "comes later" than bK. In case of two same pieces like KRRvK we want to + // place the two Rs "together". If we have 62 squares left, we can place two + // Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be + // swapped and still get the same position.) + // + // In case we have at least 3 unique pieces (inlcuded kings) we encode them + // together. + if (entry->hasUniquePieces) { + + int adjust1 = squares[1] > squares[0]; + int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]); + + // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3 + // triangle to 0...5. There are 63 squares for second piece and and 62 + // (mapped to 0...61) for the third. + if (off_A1H8(squares[0])) + idx = ( MapA1D1D4[squares[0]] * 63 + + (squares[1] - adjust1)) * 62 + + squares[2] - adjust2; + + // First piece is on a1-h8 diagonal, second below: map this occurence to + // 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal + // to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27. + else if (off_A1H8(squares[1])) + idx = ( 6 * 63 + rank_of(squares[0]) * 28 + + MapB1H1H7[squares[1]]) * 62 + + squares[2] - adjust2; + + // First two pieces are on a1-h8 diagonal, third below + else if (off_A1H8(squares[2])) + idx = 6 * 63 * 62 + 4 * 28 * 62 + + rank_of(squares[0]) * 7 * 28 + + (rank_of(squares[1]) - adjust1) * 28 + + MapB1H1H7[squares[2]]; + + // All 3 pieces on the diagonal a1-h8 + else + idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + + rank_of(squares[0]) * 7 * 6 + + (rank_of(squares[1]) - adjust1) * 6 + + (rank_of(squares[2]) - adjust2); + } else + // We don't have at least 3 unique pieces, like in KRRvKBB, just map + // the kings. + idx = MapKK[MapA1D1D4[squares[0]]][squares[1]]; + +encode_remaining: + idx *= d->groupIdx[0]; + Square* groupSq = squares + d->groupLen[0]; + + // Encode remainig pawns then pieces according to square, in ascending order + bool remainingPawns = entry->hasPawns && entry->pawnCount[1]; + + while (d->groupLen[++next]) + { + std::stable_sort(groupSq, groupSq + d->groupLen[next]); + uint64_t n = 0; + + // Map down a square if "comes later" than a square in the previous + // groups (similar to what done earlier for leading group pieces). + for (int i = 0; i < d->groupLen[next]; ++i) + { + auto f = [&](Square s) { return groupSq[i] > s; }; + auto adjust = std::count_if(squares, groupSq, f); + n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns]; + } + + remainingPawns = false; + idx += n * d->groupIdx[next]; + groupSq += d->groupLen[next]; + } + + // Now that we have the index, decompress the pair and get the score + return map_score(entry, tbFile, decompress_pairs(d, idx), wdl); +} + +// Group together pieces that will be encoded together. The general rule is that +// a group contains pieces of same type and color. The exception is the leading +// group that, in case of positions withouth pawns, can be formed by 3 different +// pieces (default) or by the king pair when there is not a unique piece apart +// from the kings. When there are pawns, pawns are always first in pieces[]. +// +// As example KRKN -> KRK + N, KNNK -> KK + NN, KPPKP -> P + PP + K + K +// +// The actual grouping depends on the TB generator and can be inferred from the +// sequence of pieces in piece[] array. +template +void set_groups(T& e, PairsData* d, int order[], File f) { + + int n = 0, firstLen = e.hasPawns ? 0 : e.hasUniquePieces ? 3 : 2; + d->groupLen[n] = 1; + + // Number of pieces per group is stored in groupLen[], for instance in KRKN + // the encoder will default on '111', so groupLen[] will be (3, 1). + for (int i = 1; i < e.pieceCount; ++i) + if (--firstLen > 0 || d->pieces[i] == d->pieces[i - 1]) + d->groupLen[n]++; + else + d->groupLen[++n] = 1; + + d->groupLen[++n] = 0; // Zero-terminated + + // The sequence in pieces[] defines the groups, but not the order in which + // they are encoded. If the pieces in a group g can be combined on the board + // in N(g) different ways, then the position encoding will be of the form: + // + // g1 * N(g2) * N(g3) + g2 * N(g3) + g3 + // + // This ensures unique encoding for the whole position. The order of the + // groups is a per-table parameter and could not follow the canonical leading + // pawns/pieces -> remainig pawns -> remaining pieces. In particular the + // first group is at order[0] position and the remaining pawns, when present, + // are at order[1] position. + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + int next = pp ? 2 : 1; + int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); + uint64_t idx = 1; + + for (int k = 0; next < n || k == order[0] || k == order[1]; ++k) + if (k == order[0]) // Leading pawns or pieces + { + d->groupIdx[0] = idx; + idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] + : e.hasUniquePieces ? 31332 : 462; + } + else if (k == order[1]) // Remaining pawns + { + d->groupIdx[1] = idx; + idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]]; + } + else // Remainig pieces + { + d->groupIdx[next] = idx; + idx *= Binomial[d->groupLen[next]][freeSquares]; + freeSquares -= d->groupLen[next++]; + } + + d->groupIdx[n] = idx; +} + +// In Recursive Pairing each symbol represents a pair of childern symbols. So +// read d->btree[] symbols data and expand each one in his left and right child +// symbol until reaching the leafs that represent the symbol value. +uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) { + + visited[s] = true; // We can set it now because tree is acyclic + Sym sr = d->btree[s].get(); + + if (sr == 0xFFF) + return 0; + + Sym sl = d->btree[s].get(); + + if (!visited[sl]) + d->symlen[sl] = set_symlen(d, sl, visited); + + if (!visited[sr]) + d->symlen[sr] = set_symlen(d, sr, visited); + + return d->symlen[sl] + d->symlen[sr] + 1; +} + +uint8_t* set_sizes(PairsData* d, uint8_t* data) { + + d->flags = *data++; + + if (d->flags & TBFlag::SingleValue) { + d->blocksNum = d->blockLengthSize = 0; + d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init + d->minSymLen = *data++; // Here we store the single value + return data; + } + + // groupLen[] is a zero-terminated list of group lengths, the last groupIdx[] + // element stores the biggest index that is the tb size. + uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen]; + + d->sizeofBlock = 1ULL << *data++; + d->span = 1ULL << *data++; + d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up + auto padding = number(data++); + d->blocksNum = number(data); data += sizeof(uint32_t); + d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] + // does not point out of range. + d->maxSymLen = *data++; + d->minSymLen = *data++; + d->lowestSym = (Sym*)data; + d->base64.resize(d->maxSymLen - d->minSymLen + 1); + + // The canonical code is ordered such that longer symbols (in terms of + // the number of bits of their Huffman code) have lower numeric value, + // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). + // Starting from this we compute a base64[] table indexed by symbol length + // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. + // See http://www.eecs.harvard.edu/~michaelm/E210/huffman.pdf + for (int i = d->base64.size() - 2; i >= 0; --i) { + d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) + - number(&d->lowestSym[i + 1])) / 2; + + assert(d->base64[i] * 2 >= d->base64[i+1]); + } + + // Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more + // than d->base64[i+1] and given the above assert condition, we ensure that + // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i + // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. + for (size_t i = 0; i < d->base64.size(); ++i) + d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits + + data += d->base64.size() * sizeof(Sym); + d->symlen.resize(number(data)); data += sizeof(uint16_t); + d->btree = (LR*)data; + + // The compression scheme used is "Recursive Pairing", that replaces the most + // frequent adjacent pair of symbols in the source message by a new symbol, + // reevaluating the frequencies of all of the symbol pairs with respect to + // the extended alphabet, and then repeating the process. + // See http://www.larsson.dogma.net/dcc99.pdf + std::vector visited(d->symlen.size()); + + for (Sym sym = 0; sym < d->symlen.size(); ++sym) + if (!visited[sym]) + d->symlen[sym] = set_symlen(d, sym, visited); + + return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1); +} + +uint8_t* set_dtz_map(TBTable&, uint8_t* data, File) { return data; } + +uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { + + e.map = data; + + for (File f = FILE_A; f <= maxFile; ++f) { + auto flags = e.get(0, f)->flags; + if (flags & TBFlag::Mapped) { + if (flags & TBFlag::Wide) { + data += (uintptr_t)data & 1; // Word alignment, we may have a mixed table + for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x + e.get(0, f)->map_idx[i] = (uint16_t)((uint16_t *)data - (uint16_t *)e.map + 1); + data += 2 * number(data) + 2; + } + } + else { + for (int i = 0; i < 4; ++i) { + e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1); + data += *data + 1; + } + } + } + } + + return data += (uintptr_t)data & 1; // Word alignment +} + +// Populate entry's PairsData records with data from the just memory mapped file. +// Called at first access. +template +void set(T& e, uint8_t* data) { + + PairsData* d; + + enum { Split = 1, HasPawns = 2 }; + + assert(e.hasPawns == bool(*data & HasPawns)); + assert((e.key != e.key2) == bool(*data & Split)); + + data++; // First byte stores flags + + const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1; + const File maxFile = e.hasPawns ? FILE_D : FILE_A; + + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + + assert(!pp || e.pawnCount[0]); + + for (File f = FILE_A; f <= maxFile; ++f) { + + for (int i = 0; i < sides; i++) + *e.get(i, f) = PairsData(); + + int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF }, + { *data >> 4, pp ? *(data + 1) >> 4 : 0xF } }; + data += 1 + pp; + + for (int k = 0; k < e.pieceCount; ++k, ++data) + for (int i = 0; i < sides; i++) + e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF); + + for (int i = 0; i < sides; ++i) + set_groups(e, e.get(i, f), order[i], f); + } + + data += (uintptr_t)data & 1; // Word alignment + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) + data = set_sizes(e.get(i, f), data); + + data = set_dtz_map(e, data, maxFile); + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) { + (d = e.get(i, f))->sparseIndex = (SparseEntry*)data; + data += d->sparseIndexSize * sizeof(SparseEntry); + } + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) { + (d = e.get(i, f))->blockLength = (uint16_t*)data; + data += d->blockLengthSize * sizeof(uint16_t); + } + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) { + data = (uint8_t*)(((uintptr_t)data + 0x3F) & ~0x3F); // 64 byte alignment + (d = e.get(i, f))->data = data; + data += d->blocksNum * d->sizeofBlock; + } +} + +// If the TB file corresponding to the given position is already memory mapped +// then return its base address, otherwise try to memory map and init it. Called +// at every probe, memory map and init only at first access. Function is thread +// safe and can be called concurrently. +template +void* mapped(TBTable& e, const Position& pos) { + + static std::mutex mutex; + + // Use 'acquire' to avoid a thread reading 'ready' == true while + // another is still working. (compiler reordering may cause this). + if (e.ready.load(std::memory_order_acquire)) + return e.baseAddress; // Could be nullptr if file does not exist + + std::unique_lock lk(mutex); + + if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock + return e.baseAddress; + + // Pieces strings in decreasing order for each color, like ("KPP","KR") + std::string fname, w, b; + for (PieceType pt = KING; pt >= PAWN; --pt) { + w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]); + b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]); + } + + fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + + (Type == WDL ? ".rtbw" : ".rtbz"); + + uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type); + + if (data) + set(e, data); + + e.ready.store(true, std::memory_order_release); + return e.baseAddress; +} + +template::Ret> +Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) { + + if (pos.count() == 2) // KvK + return Ret(WDLDraw); + + TBTable* entry = TBTables.get(pos.material_key()); + + if (!entry || !mapped(*entry, pos)) + return *result = FAIL, Ret(); + + return do_probe_table(pos, entry, wdl, result); +} + +// For a position where the side to move has a winning capture it is not necessary +// to store a winning value so the generator treats such positions as "don't cares" +// and tries to assign to it a value that improves the compression ratio. Similarly, +// if the side to move has a drawing capture, then the position is at least drawn. +// If the position is won, then the TB needs to store a win value. But if the +// position is drawn, the TB may store a loss value if that is better for compression. +// All of this means that during probing, the engine must look at captures and probe +// their results and must probe the position itself. The "best" result of these +// probes is the correct result for the position. +// DTZ tables do not store values when a following move is a zeroing winning move +// (winning capture or winning pawn move). Also DTZ store wrong values for positions +// where the best move is an ep-move (even if losing). So in all these cases set +// the state to ZEROING_BEST_MOVE. +template +WDLScore search(Position& pos, ProbeState* result) { + + WDLScore value, bestValue = WDLLoss; + StateInfo st; + + auto moveList = MoveList(pos); + size_t totalCount = moveList.size(), moveCount = 0; + + for (const Move move : moveList) + { + if ( !pos.capture(move) + && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) + continue; + + moveCount++; + + pos.do_move(move, st); + value = -search(pos, result); + pos.undo_move(move); + + if (*result == FAIL) + return WDLDraw; + + if (value > bestValue) + { + bestValue = value; + + if (value >= WDLWin) + { + *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move + return value; + } + } + } + + // In case we have already searched all the legal moves we don't have to probe + // the TB because the stored score could be wrong. For instance TB tables + // do not contain information on position with ep rights, so in this case + // the result of probe_wdl_table is wrong. Also in case of only capture + // moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to + // return with ZEROING_BEST_MOVE set. + bool noMoreMoves = (moveCount && moveCount == totalCount); + + if (noMoreMoves) + value = bestValue; + else + { + value = probe_table(pos, result); + + if (*result == FAIL) + return WDLDraw; + } + + // DTZ stores a "don't care" value if bestValue is a win + if (bestValue >= value) + return *result = ( bestValue > WDLDraw + || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; + + return *result = OK, value; +} + +} // namespace + + +/// Tablebases::init() is called at startup and after every change to +/// "SyzygyPath" UCI option to (re)create the various tables. It is not thread +/// safe, nor it needs to be. +void Tablebases::init(const std::string& paths) { + + TBTables.clear(); + MaxCardinality = 0; + TBFile::Paths = paths; + + if (paths.empty() || paths == "") + return; + + // MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27 + int code = 0; + for (Square s = SQ_A1; s <= SQ_H8; ++s) + if (off_A1H8(s) < 0) + MapB1H1H7[s] = code++; + + // MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..9 + std::vector diagonal; + code = 0; + for (Square s = SQ_A1; s <= SQ_D4; ++s) + if (off_A1H8(s) < 0 && file_of(s) <= FILE_D) + MapA1D1D4[s] = code++; + + else if (!off_A1H8(s) && file_of(s) <= FILE_D) + diagonal.push_back(s); + + // Diagonal squares are encoded as last ones + for (auto s : diagonal) + MapA1D1D4[s] = code++; + + // MapKK[] encodes all the 461 possible legal positions of two kings where + // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4 + // diagonal, the other one shall not to be above the a1-h8 diagonal. + std::vector> bothOnDiagonal; + code = 0; + for (int idx = 0; idx < 10; idx++) + for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1) + if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 + { + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + if ((PseudoAttacks[KING][s1] | s1) & s2) + continue; // Illegal position + + else if (!off_A1H8(s1) && off_A1H8(s2) > 0) + continue; // First on diagonal, second above + + else if (!off_A1H8(s1) && !off_A1H8(s2)) + bothOnDiagonal.emplace_back(idx, s2); + + else + MapKK[idx][s2] = code++; + } + + // Legal positions with both kings on diagonal are encoded as last ones + for (auto p : bothOnDiagonal) + MapKK[p.first][p.second] = code++; + + // Binomial[] stores the Binomial Coefficents using Pascal rule. There + // are Binomial[k][n] ways to choose k elements from a set of n elements. + Binomial[0][0] = 1; + + for (int n = 1; n < 64; n++) // Squares + for (int k = 0; k < 6 && k <= n; ++k) // Pieces + Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0) + + (k < n ? Binomial[k ][n - 1] : 0); + + // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible + // available squares when the leading one is in 's'. Moreover the pawn with + // highest MapPawns[] is the leading pawn, the one nearest the edge and, + // among pawns with same file, the one with lowest rank. + int availableSquares = 47; // Available squares when lead pawn is in a2 + + // Init the tables for the encoding of leading pawns group: with 7-men TB we + // can have up to 5 leading pawns (KPPPPPK). + for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt) + for (File f = FILE_A; f <= FILE_D; ++f) + { + // Restart the index at every file because TB table is splitted + // by file, so we can reuse the same index for different files. + int idx = 0; + + // Sum all possible combinations for a given file, starting with + // the leading pawn on rank 2 and increasing the rank. + for (Rank r = RANK_2; r <= RANK_7; ++r) + { + Square sq = make_square(f, r); + + // Compute MapPawns[] at first pass. + // If sq is the leading pawn square, any other pawn cannot be + // below or more toward the edge of sq. There are 47 available + // squares when sq = a2 and reduced by 2 for any rank increase + // due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45 + if (leadPawnsCnt == 1) + { + MapPawns[sq] = availableSquares--; + MapPawns[flip_file(sq)] = availableSquares--; + } + LeadPawnIdx[leadPawnsCnt][sq] = idx; + idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]]; + } + // After a file is traversed, store the cumulated per-file index + LeadPawnsSize[leadPawnsCnt][f] = idx; + } + + // Add entries in TB tables if the corresponding ".rtbw" file exists + for (PieceType p1 = PAWN; p1 < KING; ++p1) { + TBTables.add({KING, p1, KING}); + + for (PieceType p2 = PAWN; p2 <= p1; ++p2) { + TBTables.add({KING, p1, p2, KING}); + TBTables.add({KING, p1, KING, p2}); + + for (PieceType p3 = PAWN; p3 < KING; ++p3) + TBTables.add({KING, p1, p2, KING, p3}); + + for (PieceType p3 = PAWN; p3 <= p2; ++p3) { + TBTables.add({KING, p1, p2, p3, KING}); + + for (PieceType p4 = PAWN; p4 <= p3; ++p4) { + TBTables.add({KING, p1, p2, p3, p4, KING}); + + for (PieceType p5 = PAWN; p5 <= p4; ++p5) + TBTables.add({KING, p1, p2, p3, p4, p5, KING}); + + for (PieceType p5 = PAWN; p5 < KING; ++p5) + TBTables.add({KING, p1, p2, p3, p4, KING, p5}); + } + + for (PieceType p4 = PAWN; p4 < KING; ++p4) { + TBTables.add({KING, p1, p2, p3, KING, p4}); + + for (PieceType p5 = PAWN; p5 <= p4; ++p5) + TBTables.add({KING, p1, p2, p3, KING, p4, p5}); + } + } + + for (PieceType p3 = PAWN; p3 <= p1; ++p3) + for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4) + TBTables.add({KING, p1, p2, KING, p3, p4}); + } + } + + sync_cout << "info string Found " << TBTables.size() << " tablebases" << sync_endl; +} + +// Probe the WDL table for a particular position. +// If *result != FAIL, the probe was successful. +// The return value is from the point of view of the side to move: +// -2 : loss +// -1 : loss, but draw under 50-move rule +// 0 : draw +// 1 : win, but draw under 50-move rule +// 2 : win +WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { + + *result = OK; + return search(pos, result); +} + +// Probe the DTZ table for a particular position. +// If *result != FAIL, the probe was successful. +// The return value is from the point of view of the side to move: +// n < -100 : loss, but draw under 50-move rule +// -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0) +// -1 : loss, the side to move is mated +// 0 : draw +// 1 < n <= 100 : win in n ply (assuming 50-move counter == 0) +// 100 < n : win, but draw under 50-move rule +// +// The return value n can be off by 1: a return value -n can mean a loss +// in n+1 ply and a return value +n can mean a win in n+1 ply. This +// cannot happen for tables with positions exactly on the "edge" of +// the 50-move rule. +// +// This implies that if dtz > 0 is returned, the position is certainly +// a win if dtz + 50-move-counter <= 99. Care must be taken that the engine +// picks moves that preserve dtz + 50-move-counter <= 99. +// +// If n = 100 immediately after a capture or pawn move, then the position +// is also certainly a win, and during the whole phase until the next +// capture or pawn move, the inequality to be preserved is +// dtz + 50-movecounter <= 100. +// +// In short, if a move is available resulting in dtz + 50-move-counter <= 99, +// then do not accept moves leading to dtz + 50-move-counter == 100. +int Tablebases::probe_dtz(Position& pos, ProbeState* result) { + + *result = OK; + WDLScore wdl = search(pos, result); + + if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws + return 0; + + // DTZ stores a 'don't care' value in this case, or even a plain wrong + // one as in case the best move is a losing ep, so it cannot be probed. + if (*result == ZEROING_BEST_MOVE) + return dtz_before_zeroing(wdl); + + int dtz = probe_table(pos, result, wdl); + + if (*result == FAIL) + return 0; + + if (*result != CHANGE_STM) + return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl); + + // DTZ stores results for the other side, so we need to do a 1-ply search and + // find the winning move that minimizes DTZ. + StateInfo st; + int minDTZ = 0xFFFF; + + for (const Move move : MoveList(pos)) + { + bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; + + pos.do_move(move, st); + + // For zeroing moves we want the dtz of the move _before_ doing it, + // otherwise we will get the dtz of the next move sequence. Search the + // position after the move to get the score sign (because even in a + // winning position we could make a losing capture or going for a draw). + dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) + : -probe_dtz(pos, result); + + // If the move mates, force minDTZ to 1 + if (dtz == 1 && pos.checkers() && MoveList(pos).size() == 0) + minDTZ = 1; + + // Convert result from 1-ply search. Zeroing moves are already accounted + // by dtz_before_zeroing() that returns the DTZ of the previous move. + if (!zeroing) + dtz += sign_of(dtz); + + // Skip the draws and if we are winning only pick positive dtz + if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl)) + minDTZ = dtz; + + pos.undo_move(move); + + if (*result == FAIL) + return 0; + } + + // When there are no legal moves, the position is mate: we return -1 + return minDTZ == 0xFFFF ? -1 : minDTZ; +} + + +// Use the DTZ tables to rank root moves. +// +// A return value false indicates that not all probes were successful. +bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { + + ProbeState result; + StateInfo st; + + // Obtain 50-move counter for the root position + int cnt50 = pos.rule50_count(); + + // Check whether a position was repeated since the last zeroing move. + bool rep = pos.has_repeated(); + + int dtz, bound = Options["Syzygy50MoveRule"] ? 900 : 1; + + // Probe and rank each move + for (auto& m : rootMoves) + { + pos.do_move(m.pv[0], st); + + // Calculate dtz for the current move counting from the root position + if (pos.rule50_count() == 0) + { + // In case of a zeroing move, dtz is one of -101/-1/0/1/101 + WDLScore wdl = -probe_wdl(pos, &result); + dtz = dtz_before_zeroing(wdl); + } + else + { + // Otherwise, take dtz for the new position and correct by 1 ply + dtz = -probe_dtz(pos, &result); + dtz = dtz > 0 ? dtz + 1 + : dtz < 0 ? dtz - 1 : dtz; + } + + // Make sure that a mating move is assigned a dtz value of 1 + if ( pos.checkers() + && dtz == 2 + && MoveList(pos).size() == 0) + dtz = 1; + + pos.undo_move(m.pv[0]); + + if (result == FAIL) + return false; + + // Better moves are ranked higher. Certain wins are ranked equally. + // Losing moves are ranked equally unless a 50-move draw is in sight. + int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? 1000 : 1000 - (dtz + cnt50)) + : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -1000 : -1000 + (-dtz + cnt50)) + : 0; + m.tbRank = r; + + // Determine the score to be displayed for this move. Assign at least + // 1 cp to cursed wins and let it grow to 49 cp as the positions gets + // closer to a real win. + m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 + : r > 0 ? Value((std::max( 3, r - 800) * int(PawnValueEg)) / 200) + : r == 0 ? VALUE_DRAW + : r > -bound ? Value((std::min(-3, r + 800) * int(PawnValueEg)) / 200) + : -VALUE_MATE + MAX_PLY + 1; + } + + return true; +} + + +// Use the WDL tables to rank root moves. +// This is a fallback for the case that some or all DTZ tables are missing. +// +// A return value false indicates that not all probes were successful. +bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { + + static const int WDL_to_rank[] = { -1000, -899, 0, 899, 1000 }; + + ProbeState result; + StateInfo st; + + bool rule50 = Options["Syzygy50MoveRule"]; + + // Probe and rank each move + for (auto& m : rootMoves) + { + pos.do_move(m.pv[0], st); + + WDLScore wdl = -probe_wdl(pos, &result); + + pos.undo_move(m.pv[0]); + + if (result == FAIL) + return false; + + m.tbRank = WDL_to_rank[wdl + 2]; + + if (!rule50) + wdl = wdl > WDLDraw ? WDLWin + : wdl < WDLDraw ? WDLLoss : WDLDraw; + m.tbScore = WDL_to_value[wdl + 2]; + } + + return true; +} diff --git a/engines/stockfish13/syzygy/tbprobe.h b/engines/stockfish13/syzygy/tbprobe.h new file mode 100644 index 0000000..b998989 --- /dev/null +++ b/engines/stockfish13/syzygy/tbprobe.h @@ -0,0 +1,78 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TBPROBE_H +#define TBPROBE_H + +#include + +#include "../search.h" + +namespace Tablebases { + +enum WDLScore { + WDLLoss = -2, // Loss + WDLBlessedLoss = -1, // Loss, but draw under 50-move rule + WDLDraw = 0, // Draw + WDLCursedWin = 1, // Win, but draw under 50-move rule + WDLWin = 2, // Win + + WDLScoreNone = -1000 +}; + +// Possible states after a probing operation +enum ProbeState { + FAIL = 0, // Probe failed (missing file table) + OK = 1, // Probe succesful + CHANGE_STM = -1, // DTZ should check the other side + ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) +}; + +extern int MaxCardinality; + +void init(const std::string& paths); +WDLScore probe_wdl(Position& pos, ProbeState* result); +int probe_dtz(Position& pos, ProbeState* result); +bool root_probe(Position& pos, Search::RootMoves& rootMoves); +bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); +void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); + +inline std::ostream& operator<<(std::ostream& os, const WDLScore v) { + + os << (v == WDLLoss ? "Loss" : + v == WDLBlessedLoss ? "Blessed loss" : + v == WDLDraw ? "Draw" : + v == WDLCursedWin ? "Cursed win" : + v == WDLWin ? "Win" : "None"); + + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const ProbeState v) { + + os << (v == FAIL ? "Failed" : + v == OK ? "Success" : + v == CHANGE_STM ? "Probed opponent side" : + v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None"); + + return os; +} + +} + +#endif diff --git a/engines/stockfish13/thread.cpp b/engines/stockfish13/thread.cpp new file mode 100644 index 0000000..b46fce5 --- /dev/null +++ b/engines/stockfish13/thread.cpp @@ -0,0 +1,260 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include // For std::count +#include "movegen.h" +#include "search.h" +#include "thread.h" +#include "uci.h" +#include "syzygy/tbprobe.h" +#include "tt.h" + +ThreadPool Threads; // Global object + + +/// Thread constructor launches the thread and waits until it goes to sleep +/// in idle_loop(). Note that 'searching' and 'exit' should be already set. + +Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { + + wait_for_search_finished(); +} + + +/// Thread destructor wakes up the thread in idle_loop() and waits +/// for its termination. Thread should be already waiting. + +Thread::~Thread() { + + assert(!searching); + + exit = true; + start_searching(); + stdThread.join(); +} + + +/// Thread::clear() reset histories, usually before a new game + +void Thread::clear() { + + counterMoves.fill(MOVE_NONE); + mainHistory.fill(0); + lowPlyHistory.fill(0); + captureHistory.fill(0); + + for (bool inCheck : { false, true }) + for (StatsType c : { NoCaptures, Captures }) + { + for (auto& to : continuationHistory[inCheck][c]) + for (auto& h : to) + h->fill(0); + continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1); + } +} + + +/// Thread::start_searching() wakes up the thread that will start the search + +void Thread::start_searching() { + + std::lock_guard lk(mutex); + searching = true; + cv.notify_one(); // Wake up the thread in idle_loop() +} + + +/// Thread::wait_for_search_finished() blocks on the condition variable +/// until the thread has finished searching. + +void Thread::wait_for_search_finished() { + + std::unique_lock lk(mutex); + cv.wait(lk, [&]{ return !searching; }); +} + + +/// Thread::idle_loop() is where the thread is parked, blocked on the +/// condition variable, when it has no work to do. + +void Thread::idle_loop() { + + // If OS already scheduled us on a different group than 0 then don't overwrite + // the choice, eventually we are one of many one-threaded processes running on + // some Windows NUMA hardware, for instance in fishtest. To make it simple, + // just check if running threads are below a threshold, in this case all this + // NUMA machinery is not needed. + if (Options["Threads"] > 8) + WinProcGroup::bindThisThread(idx); + + while (true) + { + std::unique_lock lk(mutex); + searching = false; + cv.notify_one(); // Wake up anyone waiting for search finished + cv.wait(lk, [&]{ return searching; }); + + if (exit) + return; + + lk.unlock(); + + search(); + } +} + +/// ThreadPool::set() creates/destroys threads to match the requested number. +/// Created and launched threads will immediately go to sleep in idle_loop. +/// Upon resizing, threads are recreated to allow for binding if necessary. + +void ThreadPool::set(size_t requested) { + + if (size() > 0) { // destroy any existing thread(s) + main()->wait_for_search_finished(); + + while (size() > 0) + delete back(), pop_back(); + } + + if (requested > 0) { // create new thread(s) + push_back(new MainThread(0)); + + while (size() < requested) + push_back(new Thread(size())); + clear(); + + // Reallocate the hash with the new threadpool size + TT.resize(size_t(Options["Hash"])); + + // Init thread number dependent search params. + Search::init(); + } +} + + +/// ThreadPool::clear() sets threadPool data to initial values + +void ThreadPool::clear() { + + for (Thread* th : *this) + th->clear(); + + main()->callsCnt = 0; + main()->bestPreviousScore = VALUE_INFINITE; + main()->previousTimeReduction = 1.0; +} + + +/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and +/// returns immediately. Main thread will wake up other threads and start the search. + +void ThreadPool::start_thinking(Position& pos, StateListPtr& states, + const Search::LimitsType& limits, bool ponderMode) { + + main()->wait_for_search_finished(); + + main()->stopOnPonderhit = stop = false; + increaseDepth = true; + main()->ponder = ponderMode; + Search::Limits = limits; + Search::RootMoves rootMoves; + + for (const auto& m : MoveList(pos)) + if ( limits.searchmoves.empty() + || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) + rootMoves.emplace_back(m); + + if (!rootMoves.empty()) + Tablebases::rank_root_moves(pos, rootMoves); + + // After ownership transfer 'states' becomes empty, so if we stop the search + // and call 'go' again without setting a new position states.get() == NULL. + assert(states.get() || setupStates.get()); + + if (states.get()) + setupStates = std::move(states); // Ownership transfer, states is now empty + + // We use Position::set() to set root position across threads. But there are + // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot + // be deduced from a fen string, so set() clears them and they are set from + // setupStates->back() later. The rootState is per thread, earlier states are shared + // since they are read-only. + for (Thread* th : *this) + { + th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0; + th->rootDepth = th->completedDepth = 0; + th->rootMoves = rootMoves; + th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); + th->rootState = setupStates->back(); + } + + main()->start_searching(); +} + +Thread* ThreadPool::get_best_thread() const { + + Thread* bestThread = front(); + std::map votes; + Value minScore = VALUE_NONE; + + // Find minimum score of all threads + for (Thread* th: *this) + minScore = std::min(minScore, th->rootMoves[0].score); + + // Vote according to score and depth, and select the best thread + for (Thread* th : *this) + { + votes[th->rootMoves[0].pv[0]] += + (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); + + if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) + { + // Make sure we pick the shortest mate / TB conversion or stave off mate the longest + if (th->rootMoves[0].score > bestThread->rootMoves[0].score) + bestThread = th; + } + else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY + || ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY + && votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]])) + bestThread = th; + } + + return bestThread; +} + + +/// Start non-main threads + +void ThreadPool::start_searching() { + + for (Thread* th : *this) + if (th != front()) + th->start_searching(); +} + + +/// Wait for non-main threads + +void ThreadPool::wait_for_search_finished() const { + + for (Thread* th : *this) + if (th != front()) + th->wait_for_search_finished(); +} diff --git a/engines/stockfish13/thread.h b/engines/stockfish13/thread.h new file mode 100644 index 0000000..34b9901 --- /dev/null +++ b/engines/stockfish13/thread.h @@ -0,0 +1,130 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef THREAD_H_INCLUDED +#define THREAD_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "material.h" +#include "movepick.h" +#include "pawns.h" +#include "position.h" +#include "search.h" +#include "thread_win32_osx.h" + + +/// Thread class keeps together all the thread-related stuff. We use +/// per-thread pawn and material hash tables so that once we get a +/// pointer to an entry its life time is unlimited and we don't have +/// to care about someone changing the entry under our feet. + +class Thread { + + std::mutex mutex; + std::condition_variable cv; + size_t idx; + bool exit = false, searching = true; // Set before starting std::thread + NativeThread stdThread; + +public: + explicit Thread(size_t); + virtual ~Thread(); + virtual void search(); + void clear(); + void idle_loop(); + void start_searching(); + void wait_for_search_finished(); + + Pawns::Table pawnsTable; + Material::Table materialTable; + size_t pvIdx, pvLast; + uint64_t ttHitAverage; + int selDepth, nmpMinPly; + Color nmpColor; + std::atomic nodes, tbHits, bestMoveChanges; + + Position rootPos; + StateInfo rootState; + Search::RootMoves rootMoves; + Depth rootDepth, completedDepth; + CounterMoveHistory counterMoves; + ButterflyHistory mainHistory; + LowPlyHistory lowPlyHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; + Score contempt; +}; + + +/// MainThread is a derived class specific for main thread + +struct MainThread : public Thread { + + using Thread::Thread; + + void search() override; + void check_time(); + + double previousTimeReduction; + Value bestPreviousScore; + Value iterValue[4]; + int callsCnt; + bool stopOnPonderhit; + std::atomic_bool ponder; +}; + + +/// ThreadPool struct handles all the threads-related stuff like init, starting, +/// parking and, most importantly, launching a thread. All the access to threads +/// is done through this class. + +struct ThreadPool : public std::vector { + + void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); + void clear(); + void set(size_t); + + MainThread* main() const { return static_cast(front()); } + uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } + uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; + + std::atomic_bool stop, increaseDepth; + +private: + StateListPtr setupStates; + + uint64_t accumulate(std::atomic Thread::* member) const { + + uint64_t sum = 0; + for (Thread* th : *this) + sum += (th->*member).load(std::memory_order_relaxed); + return sum; + } +}; + +extern ThreadPool Threads; + +#endif // #ifndef THREAD_H_INCLUDED diff --git a/engines/stockfish13/thread_win32_osx.h b/engines/stockfish13/thread_win32_osx.h new file mode 100644 index 0000000..75ef5d9 --- /dev/null +++ b/engines/stockfish13/thread_win32_osx.h @@ -0,0 +1,66 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef THREAD_WIN32_OSX_H_INCLUDED +#define THREAD_WIN32_OSX_H_INCLUDED + +#include + +/// On OSX threads other than the main thread are created with a reduced stack +/// size of 512KB by default, this is too low for deep searches, which require +/// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE. +/// The implementation calls pthread_create() with the stack size parameter +/// equal to the linux 8MB default, on platforms that support it. + +#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS) + +#include + +static const size_t TH_STACK_SIZE = 8 * 1024 * 1024; + +template > +void* start_routine(void* ptr) +{ + P* p = reinterpret_cast(ptr); + (p->first->*(p->second))(); // Call member function pointer + delete p; + return NULL; +} + +class NativeThread { + + pthread_t thread; + +public: + template> + explicit NativeThread(void(T::*fun)(), T* obj) { + pthread_attr_t attr_storage, *attr = &attr_storage; + pthread_attr_init(attr); + pthread_attr_setstacksize(attr, TH_STACK_SIZE); + pthread_create(&thread, attr, start_routine, new P(obj, fun)); + } + void join() { pthread_join(thread, NULL); } +}; + +#else // Default case: use STL classes + +typedef std::thread NativeThread; + +#endif + +#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED diff --git a/engines/stockfish13/timeman.cpp b/engines/stockfish13/timeman.cpp new file mode 100644 index 0000000..6d9c95e --- /dev/null +++ b/engines/stockfish13/timeman.cpp @@ -0,0 +1,97 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "search.h" +#include "timeman.h" +#include "uci.h" + +TimeManagement Time; // Our global time management object + + +/// TimeManagement::init() is called at the beginning of the search and calculates +/// the bounds of time allowed for the current game ply. We currently support: +// 1) x basetime (+ z increment) +// 2) x moves in y seconds (+ z increment) + +void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { + + TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); + TimePoint slowMover = TimePoint(Options["Slow Mover"]); + TimePoint npmsec = TimePoint(Options["nodestime"]); + + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; + + // If we have to play in 'nodes as time' mode, then convert from time + // to nodes, and use resulting values in time management formulas. + // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) + // must be much lower than the real engine speed. + if (npmsec) + { + if (!availableNodes) // Only once at game start + availableNodes = npmsec * limits.time[us]; // Time is in msec + + // Convert from milliseconds to nodes + limits.time[us] = TimePoint(availableNodes); + limits.inc[us] *= npmsec; + limits.npmsec = npmsec; + } + + startTime = limits.startTime; + + // Maximum move horizon of 50 moves + int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; + + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = std::max(TimePoint(1), + limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); + + // A user may scale time usage by setting UCI option "Slow Mover" + // Default is 100 and changing this value will probably lose elo. + timeLeft = slowMover * timeLeft / 100; + + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed actual available + // game time for the current move, so also cap to 20% of available game time. + if (limits.movestogo == 0) + { + optScale = std::min(0.008 + std::pow(ply + 3.0, 0.5) / 250.0, + 0.2 * limits.time[us] / double(timeLeft)); + maxScale = std::min(7.0, 4.0 + ply / 12.0); + } + + // x moves in y seconds (+ z increment) + else + { + optScale = std::min((0.8 + ply / 128.0) / mtg, + 0.8 * limits.time[us] / double(timeLeft)); + maxScale = std::min(6.3, 1.5 + 0.11 * mtg); + } + + // Never use more than 80% of the available time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)); + + if (Options["Ponder"]) + optimumTime += optimumTime / 4; +} diff --git a/engines/stockfish13/timeman.h b/engines/stockfish13/timeman.h new file mode 100644 index 0000000..5ad72b3 --- /dev/null +++ b/engines/stockfish13/timeman.h @@ -0,0 +1,47 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TIMEMAN_H_INCLUDED +#define TIMEMAN_H_INCLUDED + +#include "misc.h" +#include "search.h" +#include "thread.h" + +/// The TimeManagement class computes the optimal time to think depending on +/// the maximum available time, the game move number and other parameters. + +class TimeManagement { +public: + void init(Search::LimitsType& limits, Color us, int ply); + TimePoint optimum() const { return optimumTime; } + TimePoint maximum() const { return maximumTime; } + TimePoint elapsed() const { return Search::Limits.npmsec ? + TimePoint(Threads.nodes_searched()) : now() - startTime; } + + int64_t availableNodes; // When in 'nodes as time' mode + +private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; +}; + +extern TimeManagement Time; + +#endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/engines/stockfish13/tt.cpp b/engines/stockfish13/tt.cpp new file mode 100644 index 0000000..dea7c71 --- /dev/null +++ b/engines/stockfish13/tt.cpp @@ -0,0 +1,157 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include // For std::memset +#include +#include + +#include "bitboard.h" +#include "misc.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" + +TranspositionTable TT; // Our global transposition table + +/// TTEntry::save() populates the TTEntry with a new node's data, possibly +/// overwriting an old position. Update is not atomic and can be racy. + +void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { + + // Preserve any existing move for the same position + if (m || (uint16_t)k != key16) + move16 = (uint16_t)m; + + // Overwrite less valuable entries (cheapest checks first) + if (b == BOUND_EXACT + || (uint16_t)k != key16 + || d - DEPTH_OFFSET > depth8 - 4) + { + assert(d > DEPTH_OFFSET); + assert(d < 256 + DEPTH_OFFSET); + + key16 = (uint16_t)k; + depth8 = (uint8_t)(d - DEPTH_OFFSET); + genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b); + value16 = (int16_t)v; + eval16 = (int16_t)ev; + } +} + + +/// TranspositionTable::resize() sets the size of the transposition table, +/// measured in megabytes. Transposition table consists of a power of 2 number +/// of clusters and each cluster consists of ClusterSize number of TTEntry. + +void TranspositionTable::resize(size_t mbSize) { + + Threads.main()->wait_for_search_finished(); + + aligned_large_pages_free(table); + + clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); + + table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); + if (!table) + { + std::cerr << "Failed to allocate " << mbSize + << "MB for transposition table." << std::endl; + exit(EXIT_FAILURE); + } + + clear(); +} + + +/// TranspositionTable::clear() initializes the entire transposition table to zero, +// in a multi-threaded way. + +void TranspositionTable::clear() { + + std::vector threads; + + for (size_t idx = 0; idx < Options["Threads"]; ++idx) + { + threads.emplace_back([this, idx]() { + + // Thread binding gives faster search on systems with a first-touch policy + if (Options["Threads"] > 8) + WinProcGroup::bindThisThread(idx); + + // Each thread will zero its part of the hash table + const size_t stride = size_t(clusterCount / Options["Threads"]), + start = size_t(stride * idx), + len = idx != Options["Threads"] - 1 ? + stride : clusterCount - start; + + std::memset(&table[start], 0, len * sizeof(Cluster)); + }); + } + + for (std::thread& th : threads) + th.join(); +} + + +/// TranspositionTable::probe() looks up the current position in the transposition +/// table. It returns true and a pointer to the TTEntry if the position is found. +/// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry +/// to be replaced later. The replace value of an entry is calculated as its depth +/// minus 8 times its relative age. TTEntry t1 is considered more valuable than +/// TTEntry t2 if its replace value is greater than that of t2. + +TTEntry* TranspositionTable::probe(const Key key, bool& found) const { + + TTEntry* const tte = first_entry(key); + const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster + + for (int i = 0; i < ClusterSize; ++i) + if (tte[i].key16 == key16 || !tte[i].depth8) + { + tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & 0x7)); // Refresh + + return found = (bool)tte[i].depth8, &tte[i]; + } + + // Find an entry to be replaced according to the replacement strategy + TTEntry* replace = tte; + for (int i = 1; i < ClusterSize; ++i) + // Due to our packed storage format for generation and its cyclic + // nature we add 263 (256 is the modulus plus 7 to keep the unrelated + // lowest three bits from affecting the result) to calculate the entry + // age correctly even after generation8 overflows into the next cycle. + if ( replace->depth8 - ((263 + generation8 - replace->genBound8) & 0xF8) + > tte[i].depth8 - ((263 + generation8 - tte[i].genBound8) & 0xF8)) + replace = &tte[i]; + + return found = false, replace; +} + + +/// TranspositionTable::hashfull() returns an approximation of the hashtable +/// occupation during a search. The hash is x permill full, as per UCI protocol. + +int TranspositionTable::hashfull() const { + + int cnt = 0; + for (int i = 0; i < 1000; ++i) + for (int j = 0; j < ClusterSize; ++j) + cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & 0xF8) == generation8; + + return cnt / ClusterSize; +} diff --git a/engines/stockfish13/tt.h b/engines/stockfish13/tt.h new file mode 100644 index 0000000..6aa066c --- /dev/null +++ b/engines/stockfish13/tt.h @@ -0,0 +1,97 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TT_H_INCLUDED +#define TT_H_INCLUDED + +#include "misc.h" +#include "types.h" + +/// TTEntry struct is the 10 bytes transposition table entry, defined as below: +/// +/// key 16 bit +/// depth 8 bit +/// generation 5 bit +/// pv node 1 bit +/// bound type 2 bit +/// move 16 bit +/// value 16 bit +/// eval value 16 bit + +struct TTEntry { + + Move move() const { return (Move )move16; } + Value value() const { return (Value)value16; } + Value eval() const { return (Value)eval16; } + Depth depth() const { return (Depth)depth8 + DEPTH_OFFSET; } + bool is_pv() const { return (bool)(genBound8 & 0x4); } + Bound bound() const { return (Bound)(genBound8 & 0x3); } + void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); + +private: + friend class TranspositionTable; + + uint16_t key16; + uint8_t depth8; + uint8_t genBound8; + uint16_t move16; + int16_t value16; + int16_t eval16; +}; + + +/// A TranspositionTable is an array of Cluster, of size clusterCount. Each +/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry +/// contains information on exactly one position. The size of a Cluster should +/// divide the size of a cache line for best performance, as the cacheline is +/// prefetched when possible. + +class TranspositionTable { + + static constexpr int ClusterSize = 3; + + struct Cluster { + TTEntry entry[ClusterSize]; + char padding[2]; // Pad to 32 bytes + }; + + static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); + +public: + ~TranspositionTable() { aligned_large_pages_free(table); } + void new_search() { generation8 += 8; } // Lower 3 bits are used by PV flag and Bound + TTEntry* probe(const Key key, bool& found) const; + int hashfull() const; + void resize(size_t mbSize); + void clear(); + + TTEntry* first_entry(const Key key) const { + return &table[mul_hi64(key, clusterCount)].entry[0]; + } + +private: + friend struct TTEntry; + + size_t clusterCount; + Cluster* table; + uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 +}; + +extern TranspositionTable TT; + +#endif // #ifndef TT_H_INCLUDED diff --git a/engines/stockfish13/tune.cpp b/engines/stockfish13/tune.cpp new file mode 100644 index 0000000..e94f67f --- /dev/null +++ b/engines/stockfish13/tune.cpp @@ -0,0 +1,144 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "types.h" +#include "misc.h" +#include "uci.h" + +using std::string; + +bool Tune::update_on_last; +const UCI::Option* LastOption = nullptr; +BoolConditions Conditions; +static std::map TuneResults; + +string Tune::next(string& names, bool pop) { + + string name; + + do { + string token = names.substr(0, names.find(',')); + + if (pop) + names.erase(0, token.size() + 1); + + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace + + } while ( std::count(name.begin(), name.end(), '(') + - std::count(name.begin(), name.end(), ')')); + + return name; +} + +static void on_tune(const UCI::Option& o) { + + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); +} + +static void make_option(const string& n, int v, const SetRange& r) { + + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; + + if (TuneResults.count(n)) + v = TuneResults[n]; + + Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune); + LastOption = &Options[n]; + + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," + << v << "," + << r(v).first << "," << r(v).second << "," + << (r(v).second - r(v).first) / 20.0 << "," + << "0.0020" + << std::endl; +} + +template<> void Tune::Entry::init_option() { make_option(name, value, range); } + +template<> void Tune::Entry::read_option() { + if (Options.count(name)) + value = int(Options[name]); +} + +template<> void Tune::Entry::init_option() { make_option(name, value, range); } + +template<> void Tune::Entry::read_option() { + if (Options.count(name)) + value = Value(int(Options[name])); +} + +template<> void Tune::Entry::init_option() { + make_option("m" + name, mg_value(value), range); + make_option("e" + name, eg_value(value), range); +} + +template<> void Tune::Entry::read_option() { + if (Options.count("m" + name)) + value = make_score(int(Options["m" + name]), eg_value(value)); + + if (Options.count("e" + name)) + value = make_score(mg_value(value), int(Options["e" + name])); +} + +// Instead of a variable here we have a PostUpdate function: just call it +template<> void Tune::Entry::init_option() {} +template<> void Tune::Entry::read_option() { value(); } + + +// Set binary conditions according to a probability that depends +// on the corresponding parameter value. + +void BoolConditions::set() { + + static PRNG rng(now()); + static bool startup = true; // To workaround fishtest bench + + for (size_t i = 0; i < binary.size(); i++) + binary[i] = !startup && (values[i] + int(rng.rand() % variance) > threshold); + + startup = false; + + for (size_t i = 0; i < binary.size(); i++) + sync_cout << binary[i] << sync_endl; +} + + +// Init options with tuning session results instead of default values. Useful to +// get correct bench signature after a tuning session or to test tuned values. +// Just copy fishtest tuning results in a result.txt file and extract the +// values with: +// +// cat results.txt | sed 's/^param: \([^,]*\), best: \([^,]*\).*/ TuneResults["\1"] = int(round(\2));/' +// +// Then paste the output below, as the function body + +#include + +void Tune::read_results() { + + /* ...insert your values here... */ +} diff --git a/engines/stockfish13/tune.h b/engines/stockfish13/tune.h new file mode 100644 index 0000000..1489fa3 --- /dev/null +++ b/engines/stockfish13/tune.h @@ -0,0 +1,193 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TUNE_H_INCLUDED +#define TUNE_H_INCLUDED + +#include +#include +#include +#include + +typedef std::pair Range; // Option's min-max values +typedef Range (RangeFun) (int); + +// Default Range function, to calculate Option's min-max values +inline Range default_range(int v) { + return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); +} + +struct SetRange { + explicit SetRange(RangeFun f) : fun(f) {} + SetRange(int min, int max) : fun(nullptr), range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } + + RangeFun* fun; + Range range; +}; + +#define SetDefaultRange SetRange(default_range) + + +/// BoolConditions struct is used to tune boolean conditions in the +/// code by toggling them on/off according to a probability that +/// depends on the value of a tuned integer parameter: for high +/// values of the parameter condition is always disabled, for low +/// values is always enabled, otherwise it is enabled with a given +/// probability that depnends on the parameter under tuning. + +struct BoolConditions { + void init(size_t size) { values.resize(size, defaultValue), binary.resize(size, 0); } + void set(); + + std::vector binary, values; + int defaultValue = 465, variance = 40, threshold = 500; + SetRange range = SetRange(0, 1000); +}; + +extern BoolConditions Conditions; + +inline void set_conditions() { Conditions.set(); } + + +/// Tune class implements the 'magic' code that makes the setup of a fishtest +/// tuning session as easy as it can be. Mainly you have just to remove const +/// qualifiers from the variables you want to tune and flag them for tuning, so +/// if you have: +/// +/// const Score myScore = S(10, 15); +/// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; +/// +/// If you have a my_post_update() function to run after values have been updated, +/// and a my_range() function to set custom Option's min-max values, then you just +/// remove the 'const' qualifiers and write somewhere below in the file: +/// +/// TUNE(SetRange(my_range), myScore, myValue, my_post_update); +/// +/// You can also set the range directly, and restore the default at the end +/// +/// TUNE(SetRange(-100, 100), myScore, SetDefaultRange); +/// +/// In case update function is slow and you have many parameters, you can add: +/// +/// UPDATE_ON_LAST(); +/// +/// And the values update, including post update function call, will be done only +/// once, after the engine receives the last UCI option, that is the one defined +/// and created as the last one, so the GUI should send the options in the same +/// order in which have been defined. + +class Tune { + + typedef void (PostUpdate) (); // Post-update function + + Tune() { read_results(); } + Tune(const Tune&) = delete; + void operator=(const Tune&) = delete; + void read_results(); + + static Tune& instance() { static Tune t; return t; } // Singleton + + // Use polymorphism to accomodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; + + template + struct Entry : public EntryBase { + + static_assert(!std::is_const::value, "Parameter cannot be const!"); + + static_assert( std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value, "Parameter type not supported!"); + + Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {} + void operator=(const Entry&) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; + + std::string name; + T& value; + SetRange range; + }; + + // Our facilty to fill the container, each Entry corresponds to a parameter to tune. + // We use variadic templates to deal with an unspecified number of entries, each one + // of a possible different type. + static std::string next(std::string& names, bool pop = true); + + int add(const SetRange&, std::string&&) { return 0; } + + template + int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { + list.push_back(std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } + + // Template specialization for arrays: recursively handle multi-dimensional arrays + template + int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); + return add(range, std::move(names), args...); + } + + // Template specialization for SetRange + template + int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { + return add(value, (next(names), std::move(names)), args...); + } + + // Template specialization for BoolConditions + template + int add(const SetRange& range, std::string&& names, BoolConditions& cond, Args&&... args) { + for (size_t size = cond.values.size(), i = 0; i < size; i++) + add(cond.range, next(names, i == size - 1) + "_" + std::to_string(i), cond.values[i]); + return add(range, std::move(names), args...); + } + + std::vector> list; + +public: + template + static int add(const std::string& names, Args&&... args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis + } + static void init() { for (auto& e : instance().list) e->init_option(); read_options(); } // Deferred, due to UCI::Options access + static void read_options() { for (auto& e : instance().list) e->read_option(); } + static bool update_on_last; +}; + +// Some macro magic :-) we define a dummy int variable that compiler initializes calling Tune::add() +#define STRINGIFY(x) #x +#define UNIQUE2(x, y) x ## y +#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ +#define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__) + +#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true + +// Some macro to tune toggling of boolean conditions +#define CONDITION(x) (Conditions.binary[__COUNTER__] || (x)) +#define TUNE_CONDITIONS() int UNIQUE(c, __LINE__) = (Conditions.init(__COUNTER__), 0); \ + TUNE(Conditions, set_conditions) + +#endif // #ifndef TUNE_H_INCLUDED diff --git a/engines/stockfish13/types.h b/engines/stockfish13/types.h new file mode 100644 index 0000000..5873c69 --- /dev/null +++ b/engines/stockfish13/types.h @@ -0,0 +1,481 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TYPES_H_INCLUDED +#define TYPES_H_INCLUDED + +/// When compiling with provided Makefile (e.g. for Linux and OSX), configuration +/// is done automatically. To get started type 'make help'. +/// +/// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches +/// need to be set manually: +/// +/// -DNDEBUG | Disable debugging mode. Always use this for release. +/// +/// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to +/// | run on some very old machines. +/// +/// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works +/// | only in 64-bit mode and requires hardware with popcnt support. +/// +/// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works +/// | only in 64-bit mode and requires hardware with pext support. + +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +// Disable some silly and noisy warning from MSVC compiler +#pragma warning(disable: 4127) // Conditional expression is constant +#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type +#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' +#endif + +/// Predefined macros hell: +/// +/// __GNUC__ Compiler is gcc, Clang or Intel on Linux +/// __INTEL_COMPILER Compiler is Intel +/// _MSC_VER Compiler is MSVC or Intel on Windows +/// _WIN32 Building on Windows (any) +/// _WIN64 Building on Windows 64 bit + +#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used +# include // Microsoft header for _BitScanForward64() +# define IS_64BIT +#endif + +#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) +# include // Intel and Microsoft header for _mm_popcnt_u64() +#endif + +#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) +# include // Intel and Microsoft header for _mm_prefetch() +#endif + +#if defined(USE_PEXT) +# include // Header for _pext_u64() intrinsic +# define pext(b, m) _pext_u64(b, m) +#else +# define pext(b, m) 0 +#endif + +#ifdef USE_POPCNT +constexpr bool HasPopCnt = true; +#else +constexpr bool HasPopCnt = false; +#endif + +#ifdef USE_PEXT +constexpr bool HasPext = true; +#else +constexpr bool HasPext = false; +#endif + +#ifdef IS_64BIT +constexpr bool Is64Bit = true; +#else +constexpr bool Is64Bit = false; +#endif + +typedef uint64_t Key; +typedef uint64_t Bitboard; + +constexpr int MAX_MOVES = 256; +constexpr int MAX_PLY = 246; + +/// A move needs 16 bits to be stored +/// +/// bit 0- 5: destination square (from 0 to 63) +/// bit 6-11: origin square (from 0 to 63) +/// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +/// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +/// NOTE: EN-PASSANT bit is set only when a pawn can be captured +/// +/// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in +/// any normal move destination square is always different from origin square +/// while MOVE_NONE and MOVE_NULL have the same origin and destination square. + +enum Move : int { + MOVE_NONE, + MOVE_NULL = 65 +}; + +enum MoveType { + NORMAL, + PROMOTION = 1 << 14, + ENPASSANT = 2 << 14, + CASTLING = 3 << 14 +}; + +enum Color { + WHITE, BLACK, COLOR_NB = 2 +}; + +enum CastlingRights { + NO_CASTLING, + WHITE_OO, + WHITE_OOO = WHITE_OO << 1, + BLACK_OO = WHITE_OO << 2, + BLACK_OOO = WHITE_OO << 3, + + KING_SIDE = WHITE_OO | BLACK_OO, + QUEEN_SIDE = WHITE_OOO | BLACK_OOO, + WHITE_CASTLING = WHITE_OO | WHITE_OOO, + BLACK_CASTLING = BLACK_OO | BLACK_OOO, + ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, + + CASTLING_RIGHT_NB = 16 +}; + +enum Phase { + PHASE_ENDGAME, + PHASE_MIDGAME = 128, + MG = 0, EG = 1, PHASE_NB = 2 +}; + +enum ScaleFactor { + SCALE_FACTOR_DRAW = 0, + SCALE_FACTOR_NORMAL = 64, + SCALE_FACTOR_MAX = 128, + SCALE_FACTOR_NONE = 255 +}; + +enum Bound { + BOUND_NONE, + BOUND_UPPER, + BOUND_LOWER, + BOUND_EXACT = BOUND_UPPER | BOUND_LOWER +}; + +enum Value : int { + VALUE_ZERO = 0, + VALUE_DRAW = 0, + VALUE_KNOWN_WIN = 10000, + VALUE_MATE = 32000, + VALUE_INFINITE = 32001, + VALUE_NONE = 32002, + + VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, + VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, + VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, + VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, + + PawnValueMg = 126, PawnValueEg = 208, + KnightValueMg = 781, KnightValueEg = 854, + BishopValueMg = 825, BishopValueEg = 915, + RookValueMg = 1276, RookValueEg = 1380, + QueenValueMg = 2538, QueenValueEg = 2682, + Tempo = 28, + + MidgameLimit = 15258, EndgameLimit = 3915 +}; + +enum PieceType { + NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, + ALL_PIECES = 0, + PIECE_TYPE_NB = 8 +}; + +enum Piece { + NO_PIECE, + W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, + PIECE_NB = 16 +}; + +constexpr Value PieceValue[PHASE_NB][PIECE_NB] = { + { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO }, + { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO } +}; + +typedef int Depth; + +enum : int { + DEPTH_QS_CHECKS = 0, + DEPTH_QS_NO_CHECKS = -1, + DEPTH_QS_RECAPTURES = -5, + + DEPTH_NONE = -6, + + DEPTH_OFFSET = -7 // value used only for TT entry occupancy check +}; + +enum Square : int { + SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, + SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, + SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, + SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, + SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, + SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, + SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, + SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, + SQ_NONE, + + SQUARE_ZERO = 0, + SQUARE_NB = 64 +}; + +enum Direction : int { + NORTH = 8, + EAST = 1, + SOUTH = -NORTH, + WEST = -EAST, + + NORTH_EAST = NORTH + EAST, + SOUTH_EAST = SOUTH + EAST, + SOUTH_WEST = SOUTH + WEST, + NORTH_WEST = NORTH + WEST +}; + +enum File : int { + FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB +}; + +enum Rank : int { + RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB +}; + +// Keep track of what a move changes on the board (used by NNUE) +struct DirtyPiece { + + // Number of changed pieces + int dirty_num; + + // Max 3 pieces can change in one move. A promotion with capture moves + // both the pawn and the captured piece to SQ_NONE and the piece promoted + // to from SQ_NONE to the capture square. + Piece piece[3]; + + // From and to squares, which may be SQ_NONE + Square from[3]; + Square to[3]; +}; + +/// Score enum stores a middlegame and an endgame value in a single integer (enum). +/// The least significant 16 bits are used to store the middlegame value and the +/// upper 16 bits are used to store the endgame value. We have to take care to +/// avoid left-shifting a signed int to avoid undefined behavior. +enum Score : int { SCORE_ZERO }; + +constexpr Score make_score(int mg, int eg) { + return Score((int)((unsigned int)eg << 16) + mg); +} + +/// Extracting the signed lower and upper 16 bits is not so trivial because +/// according to the standard a simple cast to short is implementation defined +/// and so is a right shift of a signed integer. +inline Value eg_value(Score s) { + union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) }; + return Value(eg.s); +} + +inline Value mg_value(Score s) { + union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) }; + return Value(mg.s); +} + +#define ENABLE_BASE_OPERATORS_ON(T) \ +constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ +constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ +constexpr T operator-(T d) { return T(-int(d)); } \ +inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \ +inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; } + +#define ENABLE_INCR_OPERATORS_ON(T) \ +inline T& operator++(T& d) { return d = T(int(d) + 1); } \ +inline T& operator--(T& d) { return d = T(int(d) - 1); } + +#define ENABLE_FULL_OPERATORS_ON(T) \ +ENABLE_BASE_OPERATORS_ON(T) \ +constexpr T operator*(int i, T d) { return T(i * int(d)); } \ +constexpr T operator*(T d, int i) { return T(int(d) * i); } \ +constexpr T operator/(T d, int i) { return T(int(d) / i); } \ +constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ +inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ +inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } + +ENABLE_FULL_OPERATORS_ON(Value) +ENABLE_FULL_OPERATORS_ON(Direction) + +ENABLE_INCR_OPERATORS_ON(Piece) +ENABLE_INCR_OPERATORS_ON(PieceType) +ENABLE_INCR_OPERATORS_ON(Square) +ENABLE_INCR_OPERATORS_ON(File) +ENABLE_INCR_OPERATORS_ON(Rank) + +ENABLE_BASE_OPERATORS_ON(Score) + +#undef ENABLE_FULL_OPERATORS_ON +#undef ENABLE_INCR_OPERATORS_ON +#undef ENABLE_BASE_OPERATORS_ON + +/// Additional operators to add a Direction to a Square +constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } +constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } +inline Square& operator+=(Square& s, Direction d) { return s = s + d; } +inline Square& operator-=(Square& s, Direction d) { return s = s - d; } + +/// Only declared but not defined. We don't want to multiply two scores due to +/// a very high risk of overflow. So user should explicitly convert to integer. +Score operator*(Score, Score) = delete; + +/// Division of a Score must be handled separately for each term +inline Score operator/(Score s, int i) { + return make_score(mg_value(s) / i, eg_value(s) / i); +} + +/// Multiplication of a Score by an integer. We check for overflow in debug mode. +inline Score operator*(Score s, int i) { + + Score result = Score(int(s) * i); + + assert(eg_value(result) == (i * eg_value(s))); + assert(mg_value(result) == (i * mg_value(s))); + assert((i == 0) || (result / i) == s); + + return result; +} + +/// Multiplication of a Score by a boolean +inline Score operator*(Score s, bool b) { + return b ? s : SCORE_ZERO; +} + +constexpr Color operator~(Color c) { + return Color(c ^ BLACK); // Toggle color +} + +constexpr Square flip_rank(Square s) { // Swap A1 <-> A8 + return Square(s ^ SQ_A8); +} + +constexpr Square flip_file(Square s) { // Swap A1 <-> H1 + return Square(s ^ SQ_H1); +} + +constexpr Piece operator~(Piece pc) { + return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT +} + +constexpr CastlingRights operator&(Color c, CastlingRights cr) { + return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); +} + +constexpr Value mate_in(int ply) { + return VALUE_MATE - ply; +} + +constexpr Value mated_in(int ply) { + return -VALUE_MATE + ply; +} + +constexpr Square make_square(File f, Rank r) { + return Square((r << 3) + f); +} + +constexpr Piece make_piece(Color c, PieceType pt) { + return Piece((c << 3) + pt); +} + +constexpr PieceType type_of(Piece pc) { + return PieceType(pc & 7); +} + +inline Color color_of(Piece pc) { + assert(pc != NO_PIECE); + return Color(pc >> 3); +} + +constexpr bool is_ok(Square s) { + return s >= SQ_A1 && s <= SQ_H8; +} + +constexpr File file_of(Square s) { + return File(s & 7); +} + +constexpr Rank rank_of(Square s) { + return Rank(s >> 3); +} + +constexpr Square relative_square(Color c, Square s) { + return Square(s ^ (c * 56)); +} + +constexpr Rank relative_rank(Color c, Rank r) { + return Rank(r ^ (c * 7)); +} + +constexpr Rank relative_rank(Color c, Square s) { + return relative_rank(c, rank_of(s)); +} + +constexpr Direction pawn_push(Color c) { + return c == WHITE ? NORTH : SOUTH; +} + +constexpr Square from_sq(Move m) { + return Square((m >> 6) & 0x3F); +} + +constexpr Square to_sq(Move m) { + return Square(m & 0x3F); +} + +constexpr int from_to(Move m) { + return m & 0xFFF; +} + +constexpr MoveType type_of(Move m) { + return MoveType(m & (3 << 14)); +} + +constexpr PieceType promotion_type(Move m) { + return PieceType(((m >> 12) & 3) + KNIGHT); +} + +constexpr Move make_move(Square from, Square to) { + return Move((from << 6) + to); +} + +constexpr Move reverse_move(Move m) { + return make_move(to_sq(m), from_sq(m)); +} + +template +constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { + return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); +} + +constexpr bool is_ok(Move m) { + return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE +} + +/// Based on a congruential pseudo random number generator +constexpr Key make_key(uint64_t seed) { + return seed * 6364136223846793005ULL + 1442695040888963407ULL; +} + +#endif // #ifndef TYPES_H_INCLUDED + +#include "tune.h" // Global visibility to tuning setup diff --git a/engines/stockfish13/uci.cpp b/engines/stockfish13/uci.cpp new file mode 100644 index 0000000..e2d0c4d --- /dev/null +++ b/engines/stockfish13/uci.cpp @@ -0,0 +1,388 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include + +#include "evaluate.h" +#include "movegen.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "timeman.h" +#include "tt.h" +#include "uci.h" +#include "syzygy/tbprobe.h" + +#include "engineids.h" +void engine_message(int eid, const std::string& s); + +using namespace std; + +extern vector setup_bench(const Position&, istream&); +//void search_message(const std::string& str); + +//namespace { + + // FEN string of the initial position, normal chess + const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + + bool benchworking = false; + + + // position() is called when engine receives the "position" UCI command. + // The function sets up the position described in the given FEN string ("fen") + // or the starting position ("startpos") and then makes the moves given in the + // following move list ("moves"). + + void position(Position& pos, istringstream& is, StateListPtr& states) { + + Move m; + string token, fen; + + is >> token; + + if (token == "startpos") + { + fen = StartFEN; + is >> token; // Consume "moves" token if any + } + else if (token == "fen") + while (is >> token && token != "moves") + fen += token + " "; + else + return; + + states = StateListPtr(new std::deque(1)); // Drop old and create a new one + pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); + + // Parse move list (if any) + while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) + { + states->emplace_back(); + pos.do_move(m, states->back()); + } + } + + // trace_eval() prints the evaluation for the current position, consistent with the UCI + // options set so far. + + void trace_eval(Position& pos) { + + StateListPtr states(new std::deque(1)); + Position p; + p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); + + Eval::verify_NNUE(); + + sync_cout << "\n" << Eval::trace(p) << sync_endl; + } + + + // setoption() is called when engine receives the "setoption" UCI command. The + // function updates the UCI option ("name") to the given value ("value"). + + void setoption(istringstream& is) { + + string token, name, value; + + is >> token; // Consume "name" token + + // Read option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; + + // Read option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; + + if (Options.count(name)) + Options[name] = value; + else + sync_cout << "No such option: " << name << sync_endl; + } + + + // go() is called when engine receives the "go" UCI command. The function sets + // the thinking time and other parameters from the input string, then starts + // the search. + + void go(Position& pos, istringstream& is, StateListPtr& states) { + + Search::LimitsType limits; + string token; + bool ponderMode = false; + + limits.startTime = now(); // As early as possible! + + while (is >> token) + if (token == "searchmoves") // Needs to be the last command on the line + while (is >> token) + limits.searchmoves.push_back(UCI::to_move(pos, token)); + + else if (token == "wtime") is >> limits.time[WHITE]; + else if (token == "btime") is >> limits.time[BLACK]; + else if (token == "winc") is >> limits.inc[WHITE]; + else if (token == "binc") is >> limits.inc[BLACK]; + else if (token == "movestogo") is >> limits.movestogo; + else if (token == "depth") is >> limits.depth; + else if (token == "nodes") is >> limits.nodes; + else if (token == "movetime") is >> limits.movetime; + else if (token == "mate") is >> limits.mate; + else if (token == "perft") is >> limits.perft; + else if (token == "infinite") limits.infinite = 1; + else if (token == "ponder") ponderMode = true; + + Threads.start_thinking(pos, states, limits, ponderMode); + } + + + // bench() is called when engine receives the "bench" command. Firstly + // a list of UCI commands is setup according to bench parameters, then + // it is run one by one printing a summary at the end. + + void bench(Position& pos, istream& args, StateListPtr& states) { + + string token; + uint64_t num, nodes = 0, cnt = 1; + + vector list = setup_bench(pos, args); + num = count_if(list.begin(), list.end(), [](string s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + + TimePoint elapsed = now(); + + benchworking = true; + for (const auto& cmd : list) + { + if (!benchworking) { + break; + } + istringstream is(cmd); + is >> skipws >> token; + + if (token == "go" || token == "eval") + { + cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << endl; + if (token == "go") + { + go(pos, is, states); + Threads.main()->wait_for_search_finished(); + nodes += Threads.nodes_searched(); + } + else + trace_eval(pos); + } + else if (token == "setoption") setoption(is); + else if (token == "position") position(pos, is, states); + else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take some while + } + benchworking = false; + elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' + + dbg_print(); // Just before exiting + + auto s = "Total time (ms) : " + std::to_string(elapsed) + + "\nNodes searched : " + std::to_string(nodes) + + "\nNodes/second : " + std::to_string(1000 * nodes / elapsed); + engine_message(stockfish, s); + + cerr << "\n===========================\n" << s << std::endl; + + s = "bench END"; + std::cout << s << std::endl; + engine_message(stockfish, s); + } + + // The win rate model returns the probability (per mille) of winning given an eval + // and a game-ply. The model fits rather accurately the LTC fishtest statistics. + int win_rate_model(Value v, int ply) { + + // The model captures only up to 240 plies, so limit input (and rescale) + double m = std::min(240, ply) / 64.0; + + // Coefficients of a 3rd order polynomial fit based on fishtest data + // for two parameters needed to transform eval to the argument of a + // logistic function. + double as[] = {-8.24404295, 64.23892342, -95.73056462, 153.86478679}; + double bs[] = {-3.37154371, 28.44489198, -56.67657741, 72.05858751}; + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; + + // Transform eval to centipawns with limited range + double x = std::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0); + + // Return win rate in per mille (rounded to nearest) + return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); + } + +//} // namespace + + +/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate +/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the +/// GUI dies unexpectedly. When called with some command line arguments, e.g. to +/// run 'bench', once the command is executed the function returns immediately. +/// In addition to the UCI ones, also some additional debug commands are supported. + +void UCI::loop(int argc, char* argv[]) { + + Position pos; + string token, cmd; + StateListPtr states(new std::deque(1)); + + pos.set(StartFEN, false, &states->back(), Threads.main()); + + for (int i = 1; i < argc; ++i) + cmd += std::string(argv[i]) + " "; + + do { + if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input or EOF + cmd = "quit"; + + istringstream is(cmd); + + token.clear(); // Avoid a stale if getline() returns empty or blank line + is >> skipws >> token; + + if ( token == "quit" + || token == "stop") { + Threads.stop = true; + } + + // The GUI sends 'ponderhit' to tell us the user has played the expected move. + // So 'ponderhit' will be sent if we were told to ponder on the same move the + // user has played. We should continue searching but switch from pondering to + // normal search. + else if (token == "ponderhit") + Threads.main()->ponder = false; // Switch to normal search + + else if (token == "uci") + sync_cout << "id name " << engine_info(true) + << "\n" << Options + << "\nuciok" << sync_endl; + + else if (token == "setoption") setoption(is); + else if (token == "go") go(pos, is, states); + else if (token == "position") position(pos, is, states); + else if (token == "ucinewgame") Search::clear(); + else if (token == "isready") sync_cout << "readyok" << sync_endl; + + // Additional custom non-UCI commands, mainly for debugging. + // Do not use these commands during a search! + else if (token == "flip") pos.flip(); + else if (token == "bench") bench(pos, is, states); + else if (token == "d") sync_cout << pos << sync_endl; + else if (token == "eval") trace_eval(pos); + else if (token == "compiler") sync_cout << compiler_info() << sync_endl; + else + sync_cout << "Unknown command: " << cmd << sync_endl; + + } while (token != "quit" && argc == 1); // Command line args are one-shot +} + + +/// UCI::value() converts a Value to a string suitable for use with the UCI +/// protocol specification: +/// +/// cp The score from the engine's point of view in centipawns. +/// mate Mate in y moves, not plies. If the engine is getting mated +/// use negative values for y. + +string UCI::value(Value v) { + + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + + stringstream ss; + + if (abs(v) < VALUE_MATE_IN_MAX_PLY) + ss << "cp " << v * 100 / PawnValueEg; + else + ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; + + return ss.str(); +} + + +/// UCI::wdl() report WDL statistics given an evaluation and a game ply, based on +/// data gathered for fishtest LTC games. + +string UCI::wdl(Value v, int ply) { + + stringstream ss; + + int wdl_w = win_rate_model( v, ply); + int wdl_l = win_rate_model(-v, ply); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + + return ss.str(); +} + + +/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) + +std::string UCI::square(Square s) { + return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; +} + + +/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). +/// The only special case is castling, where we print in the e1g1 notation in +/// normal chess mode, and in e1h1 notation in chess960 mode. Internally all +/// castling moves are always encoded as 'king captures rook'. + +string UCI::move(Move m, bool chess960) { + + Square from = from_sq(m); + Square to = to_sq(m); + + if (m == MOVE_NONE) + return "(none)"; + + if (m == MOVE_NULL) + return "0000"; + + if (type_of(m) == CASTLING && !chess960) + to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); + + string move = UCI::square(from) + UCI::square(to); + + if (type_of(m) == PROMOTION) + move += " pnbrqk"[promotion_type(m)]; + + return move; +} + + +/// UCI::to_move() converts a string representing a move in coordinate notation +/// (g1f3, a7a8q) to the corresponding legal Move, if any. + +Move UCI::to_move(const Position& pos, string& str) { + + if (str.length() == 5) // Junior could send promotion piece in uppercase + str[4] = char(tolower(str[4])); + + for (const auto& m : MoveList(pos)) + if (str == UCI::move(m, pos.is_chess960())) + return m; + + return MOVE_NONE; +} diff --git a/engines/stockfish13/uci.h b/engines/stockfish13/uci.h new file mode 100644 index 0000000..3d05193 --- /dev/null +++ b/engines/stockfish13/uci.h @@ -0,0 +1,82 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef UCI_H_INCLUDED +#define UCI_H_INCLUDED + +#include +#include + +#include "types.h" + +class Position; + +namespace UCI { + +class Option; + +/// Custom comparator because UCI options should be case insensitive +struct CaseInsensitiveLess { + bool operator() (const std::string&, const std::string&) const; +}; + +/// Our options container is actually a std::map +typedef std::map OptionsMap; + +/// Option class implements an option as defined by UCI protocol +class Option { + + typedef void (*OnChange)(const Option&); + +public: + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char* v, OnChange = nullptr); + Option(double v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char* cur, OnChange = nullptr); + + Option& operator=(const std::string&); + void operator<<(const Option&); + operator double() const; + operator std::string() const; + bool operator==(const char*) const; + +private: + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; +}; + +void init(OptionsMap&); +void loop(int argc, char* argv[]); +std::string value(Value v); +std::string square(Square s); +std::string move(Move m, bool chess960); +std::string pv(const Position& pos, Depth depth, Value alpha, Value beta); +std::string wdl(Value v, int ply); +Move to_move(const Position& pos, std::string& str); +int cmd(const std::string& cmd); + +} // namespace UCI + +extern UCI::OptionsMap Options; + +#endif // #ifndef UCI_H_INCLUDED diff --git a/engines/stockfish13/ucioption.cpp b/engines/stockfish13/ucioption.cpp new file mode 100644 index 0000000..f059209 --- /dev/null +++ b/engines/stockfish13/ucioption.cpp @@ -0,0 +1,192 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "evaluate.h" +#include "misc.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "syzygy/tbprobe.h" + +using std::string; + +UCI::OptionsMap Options; // Global object + +namespace UCI { + +/// 'On change' actions, triggered by an option's value change +void on_clear_hash(const Option&) { Search::clear(); } +void on_hash_size(const Option& o) { TT.resize(size_t(o)); } +void on_logger(const Option& o) { start_logger(o); } +void on_threads(const Option& o) { Threads.set(size_t(o)); } +void on_tb_path(const Option& o) { Tablebases::init(o); } +void on_use_NNUE(const Option& ) { Eval::init_NNUE(); } +void on_eval_file(const Option& ) { Eval::init_NNUE(); } + +/// Our case insensitive less() function as required by UCI protocol +bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { + + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return tolower(c1) < tolower(c2); }); +} + + +/// UCI::init() initializes the UCI options to their hard-coded default values + +void init(OptionsMap& o) { + + constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; + + o["Debug Log File"] << Option("", on_logger); + o["Contempt"] << Option(24, -100, 100); + o["Analysis Contempt"] << Option("Both var Off var White var Black var Both", "Both"); + o["Threads"] << Option(1, 1, 512, on_threads); + o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); + o["Clear Hash"] << Option(on_clear_hash); + o["Ponder"] << Option(false); + o["MultiPV"] << Option(1, 1, 500); + o["Skill Level"] << Option(20, 0, 20); + o["Move Overhead"] << Option(10, 0, 5000); + o["Slow Mover"] << Option(100, 10, 1000); + o["nodestime"] << Option(0, 0, 10000); + o["UCI_Chess960"] << Option(false); + o["UCI_AnalyseMode"] << Option(false); + o["UCI_LimitStrength"] << Option(false); + o["UCI_Elo"] << Option(1350, 1350, 2850); + o["UCI_ShowWDL"] << Option(false); + o["SyzygyPath"] << Option("", on_tb_path); + o["SyzygyProbeDepth"] << Option(1, 1, 100); + o["Syzygy50MoveRule"] << Option(true); + o["SyzygyProbeLimit"] << Option(7, 0, 7); + o["Use NNUE"] << Option(0, 0, 2, on_use_NNUE); + o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); +} + + +/// operator<<() is used to print all the options default values in chronological +/// insertion order (the idx field) and in the format defined by the UCI protocol. + +std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { + + for (size_t idx = 0; idx < om.size(); ++idx) + for (const auto& it : om) + if (it.second.idx == idx) + { + const Option& o = it.second; + os << "\noption name " << it.first << " type " << o.type; + + if (o.type == "string" || o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; + + if (o.type == "spin") + os << " default " << int(stof(o.defaultValue)) + << " min " << o.min + << " max " << o.max; + + break; + } + + return os; +} + + +/// Option class constructors and conversion operators + +Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f) +{ defaultValue = currentValue = v; } + +Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f) +{ defaultValue = currentValue = (v ? "true" : "false"); } + +Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f) +{} + +Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f) +{ defaultValue = currentValue = std::to_string(v); } + +Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f) +{ defaultValue = v; currentValue = cur; } + +Option::operator double() const { + assert(type == "check" || type == "spin"); + return (type == "spin" ? stof(currentValue) : currentValue == "true"); +} + +Option::operator std::string() const { + assert(type == "string"); + return currentValue; +} + +bool Option::operator==(const char* s) const { + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) + && !CaseInsensitiveLess()(s, currentValue); +} + + +/// operator<<() inits options and assigns idx in the correct printing order + +void Option::operator<<(const Option& o) { + + static size_t insert_order = 0; + + *this = o; + idx = insert_order++; +} + + +/// operator=() updates currentValue and triggers on_change() action. It's up to +/// the GUI to check for option's limits, but we could receive the new value +/// from the user by console window, so let's check the bounds anyway. + +Option& Option::operator=(const string& v) { + + assert(!type.empty()); + + if ( (type != "button" && v.empty()) + || (type == "check" && v != "true" && v != "false") + || (type == "spin" && (stof(v) < min || stof(v) > max))) + return *this; + + if (type == "combo") + { + OptionsMap comboMap; // To have case insensitive compare + string token; + std::istringstream ss(defaultValue); + while (ss >> token) + comboMap[token] << Option(); + if (!comboMap.count(v) || v == "var") + return *this; + } + + if (type != "button") + currentValue = v; + + if (on_change) + on_change(*this); + + return *this; +} + +} // namespace UCI diff --git a/engines/stockfishlib.cpp b/engines/stockfishlib.cpp index 0393b6d..8f7a245 100644 --- a/engines/stockfishlib.cpp +++ b/engines/stockfishlib.cpp @@ -26,22 +26,30 @@ #include "stockfish/thread.h" -void stockfish_cmd(const char *cmd) -{ - UCI::cmd(cmd); -} +namespace Stockfish { extern const char* StartFEN; extern bool benchworking; + void setoption(std::istringstream& is); -void go(Position& pos, std::istringstream& is, StateListPtr& states); -void bench(Position& pos, std::istream& args, StateListPtr& states); -void position(Position& pos, std::istringstream& is, StateListPtr& states); -void trace_eval(Position& pos); +void go(Stockfish::Position& pos, std::istringstream& is, Stockfish::StateListPtr& states); +void bench(Stockfish::Position& pos, std::istream& args, Stockfish::StateListPtr& states); +void position(Stockfish::Position& pos, std::istringstream& is, Stockfish::StateListPtr& states); +void trace_eval(Stockfish::Position& pos); +} + void _stockfish_initialize(); +using namespace Stockfish; + +void stockfish_cmd(const char *cmd) +{ + UCI::cmd(cmd); +} + + void stockfish_initialize() { _stockfish_initialize(); } diff --git a/nnuenets/nn-03744f8d56d8.nnue b/nnuenets/nn-03744f8d56d8.nnue deleted file mode 100644 index 7c11d78..0000000 Binary files a/nnuenets/nn-03744f8d56d8.nnue and /dev/null differ diff --git a/nnuenets/nn-9e3c6298299a.nnue b/nnuenets/nn-9e3c6298299a.nnue new file mode 100644 index 0000000..bec35ac Binary files /dev/null and b/nnuenets/nn-9e3c6298299a.nnue differ