From 743c395e11a1b5c7713e1699d8e3c0d7b1eb7019 Mon Sep 17 00:00:00 2001 From: Josh-Heaps Date: Fri, 12 Jun 2026 10:51:26 -0600 Subject: [PATCH 1/2] Choose color against cpu --- JoshHeaps.Net/Controllers/ChessController.cs | 9 +++- JoshHeaps.Net/Pages/Chess.cshtml | 9 ++++ JoshHeaps.Net/Resources/chess_engine.dll | Bin 157184 -> 192512 bytes JoshHeaps.Net/wwwroot/css/chess/game.css | 41 ++++++++++++++++++ .../wwwroot/js/ChessScripts/ChessAPI.js | 4 +- .../wwwroot/js/ChessScripts/ChessModals.js | 10 +++++ .../wwwroot/js/ChessScripts/chessMain.js | 3 +- native/chess_engine/src/chess_engine.cpp | 35 +++++++++------ 8 files changed, 92 insertions(+), 19 deletions(-) diff --git a/JoshHeaps.Net/Controllers/ChessController.cs b/JoshHeaps.Net/Controllers/ChessController.cs index df9190f..46e38e9 100644 --- a/JoshHeaps.Net/Controllers/ChessController.cs +++ b/JoshHeaps.Net/Controllers/ChessController.cs @@ -35,7 +35,7 @@ public class ChessController( /// [HttpGet("new")] [HttpGet("new/{difficulty}")] - public ActionResult CreateGame(int difficulty = 20) + public ActionResult CreateGame(int difficulty = 20, string color = "random") { var gameState = chessService.CreateNewGame(); _games[gameState.GameId] = gameState; @@ -45,7 +45,12 @@ public class ChessController( gameState.BlackJoined = true; Guid playerId = Guid.NewGuid(); Guid computerId = Guid.NewGuid(); - var isWhite = Random.Shared.Next(2) == 0; + var isWhite = color.ToLowerInvariant() switch + { + "white" => true, + "black" => false, + _ => Random.Shared.Next(2) == 0, + }; gameState.Computer = engineFactory.Create(difficulty); diff --git a/JoshHeaps.Net/Pages/Chess.cshtml b/JoshHeaps.Net/Pages/Chess.cshtml index d87778c..817ae1a 100644 --- a/JoshHeaps.Net/Pages/Chess.cshtml +++ b/JoshHeaps.Net/Pages/Chess.cshtml @@ -89,6 +89,15 @@ + + @section Scripts { diff --git a/JoshHeaps.Net/Resources/chess_engine.dll b/JoshHeaps.Net/Resources/chess_engine.dll index f7f7bfdc03c585b3dac8d98228a4d63c8ea2ec43..190af57982bcd281d2a36570973b1f68c9ce2724 100644 GIT binary patch literal 192512 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 AkN^Mx delta 39207 zcmcHicU)A*_Xmvc+-2Dn>Ai@wMG+KHEZD(-u8NAiV8Li2c2uy$0*a_)T}5wnOw?GS zi7m0jh?=NaA|y6q5@TY+m~si$6g9Ez^FDKT5&V9BuirnQZ|vK!NIbzZhi&~sdt-HZo( zJ8!AL-_En#$QJOlo9cCifcuUcok_Yiul%K@Xf$IMJ4I+ZzSeNOo%TQz9$Hs&jn@=0 zjpmxWKzH<#!&yVA+)M7kTDE-Ps@2>CF)iCmGmGeZy)^L-xW;*DwnqyAwJ7n@ECOHN zkCG;Vhv}qx-d7||A~4l|Szekn?vW=2t&-yEHe3OcJ3d8BZYKq|x|jvNO_D}44cB*S zifJ^7_!m27RQ4#4+}&Vhs8*wK1mGwymjYY)$Ds;yG|h2!hKd@0i6lBsl}#Z3V1&jK z*N*ZHDbV#pDAE)GMINQej@pnqn*foH{&K7~q}g6T)Y8EJp?2CE6Q_*UXeQJF9o}o4 za2+Pk*DlSyd_kkB?DH9lbFG=Ca#SA_QePAuh5={rK=E2LfG>8SRzOq-2Nd_)qxd2X z#jhF^q0K;j+6c8ZAELI02w%L1+Snc_q$Ct~djmJP6>6hlK;`=(0RL^Di|SU=Z=H== zbTn#jv_p}V4lsQ^iY{>g>ypBWg8?@D4aLuGP_!YOfsAfPwfD!NIP8w1#Rs6?ABbWn zS(NF5B5Mo4AQDR;W)_NKFlxD#Gv@>ObA7-~K0(cYFN$x;tQTRx{Y&m-zmKA6M-)yi zfon(__BI8Wz7xge4k&I>7|&6|OLU+PBxi%k&sn`t^xKZ&Mo$z!UPN*FdlVrDQ5?yQ z0^IY9flu8kSCs1D1y<2mFIc^H@yXl5z7G%S&8Di)hL=K0GCY? z-wy?t_5#J6D=50$LJ<}N!XG57ry#E)6D&(Kx$yWaR8RQ=IbsNEKa%h-&H%qB?wzDC z#}7r~a}e6Op|-d)im5*VT#r1i9J>I8XCaCXByok@?)NK-d$Umtd4}S^1{4=RMscMl zScK*b(wuv;F3MISgkGo(qj(G=%Ct4$?ul_+c_rc#V)H>V{(KJ1CwNpeP-H;?h7->- za}+^E!1X22V~Ga&x1Op?C zC54-*b9Q?G_n3yjmU}3+5h0VT97(ksjevW1F^a#b>o)d4@t+>WYHBXS2Q?Ss8%P1| zLw@b1ejZD`(YzHy{#_r1>h^b0d_WrJQ|ooI0k)uVu!k4}V^MrbxKN^|Q&QT~a@B(6g(Xa|ZJ)Z0mcDDo&M zgD45NkiC|PsBNcFD5p~X^%CMZ*w-CJN6M6duTe}j0BlOsuNwmNS%Jd635u%`z^&O&`L~(U>{>q{2i-@p zpZZ|ZZ>U|NerZh#k2#?>Qy9om%ca=97X!kVq$Z096Uc`TX_|XTv(m5_6oV)L38bJA zu^f5|+@}>N-lxfD2et7>ZZ}YJiF|~DnbI9aoIAjFX{a5f08DL<;v9{GhQzmJG>Ypq z1DOb?r9O5gKLggHww(0c{13HDRP!N;ugQetRLp1&!e&a6xr@0oh6x5slB|nONL!VCdGk?l^m&eLQ&4Tn11kd|(3iT1T zdT2`gS4oH59l>w$xeMfB+QwLYochS?oA;A@H+9xum;}vT9?IW*QeV#K5Na$Wn2DAw z7TO3Y-2)+Bm|`$lECzzx2FhDI#JY92k~J2KJK>%O$hSI-lyd%(J9iu*CpHZVO`ixo zXYPYX8GYAkbvsy6P?GHiv&nj8)1PvWPGP<$h;A~kR$Wm^r|!a*Ske*p`2^@_Ks-r@ znDDk1=cPN~G|KLYp@we6dE%ax5BFsLQfjE&H!-4K!%&SzZI|LxXXVTI{JTP4nds%R zw5U2F+j`3T62}Kk7!N&{VehL}4Xi?2(Q-y>U%fl=%pofy$jYFz;{dn%A2K=j(YdP3 zXyUngNAA#lus($9bMMIOyU&tlImo^}+Dh#mU}c!UF6`(4j8>y$MBw{*V{+dP8!2Dl5GrS#R03cV}t+e{$d6 zZG$!~Wg2C5rPXROmm=^&D*n8(9vGfCiOrTn)7tpQ|6|wcuWEIC16s!_^3=4+4Vu)! z?zrkRlci@l66p96=xd>nK8s+6vW@`dq^+G+xjM_6`b6rjqai))cllhO0BO(@xldnz zsfm}&`UXhhPvm-i{T-fQv6V0M@ssX8CaUff;39e7%%-bcy=c6+{P;RF^!bev=iY7k zdfyO9^N;+Z?{>YG2y=dwKkk>Q-!lqz=U?S|{nI4(C-UU}d;O0+usec*PT^Ud0iHi^ z%dG~CkhcFVuNjaiJ@S^X;9~q+4jfodTH`Gz3><9S{g*vG9jZ#J`lp%;@<@Ss07jAp z8KO}Fz2yS~L#4s4^5ub}B;8+fn?a${bT2u5P=fTXi@bVJOKIU<`P`u9Qmo6W!9kLT ziyS<7ytK_(E*u<`zSNn5*uZ2?F`4oe1BQ|b%JJzLrA#CmC{d>T5~4&^E_+V#eS(lz zAmsHHAml46>|H24L)6z2#lIZrw?HI2}6K7cTFBP?5O`K#U3_nXDDoI27~zcs@!b&G~XZRp!o|| zMUM+I+P-pC4x519@)v_a9t7^XSLNUlnfi8AU;mT5Y{Xz`aFzVqh$DLAK%f@5`h-Ufn9~qDRgJz)Bq8~)7f)%a42aq36WTRHO zHlT+6y!t`dd|8atf(kJ?7?e?3xlg|cqC1xN1-+fzr!M=gacaDSf4!Z{%xJ`pfRqLVG9o0mtU=NWW#Zno*WSN`~n_3!Dlp z{vB4Tb0hI6GgKIvvpNkh5(xKo8l}@ea_%&v)aI$&acevDR-kNsKhS?xg*_m)(8r{q z&E4P1jiFQq72{Yeb;|1C|1* zxLlSu%}#{0ll=CaRzgcsFVgaj++l8-Pb}O`I;4TcM=+^l2$U(EF34NvW=My>MKg`% z&?WVyI^SaE3q>`k4&^L(0xkkmPkxZ!w4=*)yGu_pj48_JWweX+lpR*nC00+bv$Mm|d@K zDD1x39k^x}tu5Y2EGm$+hTn( zTJFEHW6PmJ(hJ;cC%%;2WUIfFTm_6xvUjvxy0TF0Lauay4M|_igVu&f56;2$C=rS* zLSOw#@V3i9ZOHC@q zuV8MB{9vySjhc*}-`P{lHfnrH*@9BY9Kni-r0eNm^|@&Dq>0r+Eh{^yB^CdXz^mC5 zF$&qV7J}dtx{ysl|!fG{LPcNyC3sy6HaVqxJPV>B5kmVGWRqnKL~rMmVVM6rgkubn3Nw*%NIWg z@*bq}K*Bc4ZlAF7QGP5?_TOrhQcq$g>5T1%o4jIMs{Udkn(A^|mOdIB*pceLpQ2_* z7r0Zi6G|wfqp!=^A2pQRPstlT8W(V>6S&$5F8hYEO!0sZqG$K+^lAMbaomA@MK;6U zI&QUEk~RM#1zv+-VTyx|a0#KX5u#7Y&vwq%FYE}}_9x}JyNoPZPRa?qRUp zOUSMKqcoXma#B%yK(%bz-$mdS#=if3ik_vqy8pe3w7`)114!Gz_$Wl)^GT@m{TI+a zviHFDP<;a4{r^(k@Aa3eWB!-wz?Z6js4wU4F-nY7x1;I6X=gjAe|8M&G=?dwib3EJgDF_k@%&PV&{yeqH6NmrD*t_8-~`X77f2dxSBTX;7IB z%9dgZ#di?QKT0LbZlG4A>?F0KwO1VM6Xf8j6N#?LouO|B*i~_Lr|0hkK85!ufNFlewpZw#5zCTRft=2@YYf z&q6E8@0EVugNSACOakhpw64nLaZ{6ie-BM(5jc?t49oK`5`t%M9B zB>KDpVOBOkk!@Ir6~TW}N%gP*9mC>DQ^;)oR4zUq?0+Q{iFqF0sEJ7oNX+j)Ri}9! z4M(@##2*5lBDiYJh4K)ApGf$daG~fBOGf1Sl`oK`S%mh zN(+pHz+aWCS=6m1g$44plg-btwLMO7IhH#*faplTVi zI~nlOf-%&*%BS@DOn&Q>Q95%?-gGKbVxP&Srt?Cf{%FoZF%a>|eZ>Y*c3*x8l`NNE%3lQtoVp`RaXlU1W<+K?)-?u@@#LrT)k0 z3mU+JS10n!m_N^`$sS_vX;j)m80j?+LP+fVk}-t^eA*eKz#8JEh$1DUD%4HR z`_ivr72UUqrXqJ+t@%%+9KXY8Sflu>I+4?sWHv@_d}f5a_e`Mv*BH3ddXN0g8Gl!= zKIki(X&d*;kI#flXI(j5_1(ctPp9b>0=ha+O@vc!>JO?Jz_mnIEqaf&oJU=m7+ zs!GR=p24gdlLuo2vP%tQ_dPabvl_^9JF*@+R|MALa{aWtpsc<$d#Aj)%uDL}iMr>F zmfep9>Zdn?X+P|in|+n6Z%g%MyXCoGeWITpMfhEEv#&?!U8z28m%QfdEIn_C`qQ1V z%h_>$KQ}<#yi>Sk+shaf@9nbX>^S}FM60_~ettGeE;#2CU!UO89fEf?ZND)IQ(UKP zCnpL@P{&zL4MNLZ^7(U_`e~7nYrI2lf4+fy@_ax5fxkoM)eXwm>IP*yZBX<-V56qA z+b+A5cM!8xGlHu=l8=_Rlvl-j>u*H>UiXna{8BglN~-txNZ#nLC&k84S}n8m4iFcf!yb!*mu=&ALvX+J~vTV0Q^4P18Swo#uixpxP9eQ091` z{}WMBZs1<8G8#kPWWks%{)im13~;xT*OH_l4@f5oSfqRW??~pQD03LyZvVJwc6U8I%h=dtV*v| zG^+L161RgE@-BC2sxiuJ$slV}jiJeA3w21c8dWhBhxB?nbg*M^mPliW*tG2+xRWWN{|Aki{SEv1hS?5Ehrh5M=RHG8DVz zIN&rnPPEEdD?g_m%|DtzxNKK!FR12rjJ82ehv1X7@?CHo$+ay@2E}6&Y_Jr}!l59B z`*ymRSCbI(f*|F$jl^JifM+Ns^S>tZgTwC7{nu8Uh6tR+Qci3?kuTz=-=KUZEVJ}9 zC`~s5OB+@?7n_WxULrWLHU>2xKS{SMYpWh=j<^qZmwpq)6rhI%S7eP>lppyy<4p7wx zc4FIy>LGmS?uv$rE(=5DZy-twys^yCA1&Kdr@=;7Vd@QaYm7_hXIHFW?>QC@?dz8oyhlha5+Mu6RJG^BfI;* z(m=)g@LAQL9c>mKwh>J8>zT#1FjpYmiiK@9C#&LYCfV9mMdk(CM1(E1B`W`QHqMeB+?&)skcblZSK4I{T^)JD%V!HvG)xr45Cp%zAH~}Qm8dHBtI{rv!-k~YY{ z-|VMfP4(Z`%Sjd9buM;5=^RVG_=nMd+;;mw_lMCK=!^ZKaKk#eZdJJS(R*^!suXGI zZh1ykZ>jZexwPtm6#TAy_)fSKw&&C9cMMFr&*k6mhD(3#k?Y=jBW>9UsI&MKU_hXI zOz0&APqj4Iq3Wh4xe{soK0%sABs}`U>56)a(i|_XC|zNz2~P#c6a>A`+D}XFHDpr$ z8oBa*1OJ{M*lj)3m6W)Q_kyhp*2oPX?3T*b$lpJ>BmJ~mKJ{?2^ph;d|9Q%HMjI%7 zDhs7T21<4B$j*QLr(fg-*>$UAm%kPLGFQ}huauwtJt8oU>RnfgfcnEerORruH@vb+ z&i*H~&Tfl<9<7u&{Ig%*&IQbUi{yci#`}A(pk{g+Om)?pN8F6!cKF3|`O2gB_1?~4 zj9DSidwfWrS{L<+%jNb@M(P>W9hb{no{aJz@}Av2q!78c)Cok3MGpFRm=u{O&;GZa zq->NA{@XD@+310})8Xr?b0Uum(YdBVUEfW>s?mIm6AKs-5w2wFwJ5Wq#Ie2y9B3uaq@-OFh4~P=2dUkruz{+ zTCR8+sb56&#&65c&l>jJgLt8f?Z?+XBKA>X%-@*9+oTwB z=sYixC5{-Vq6o~!5uD-2a>j~(`K-x}#zW&`q-q3tR*f3J-NNQ~}rMg*PvzU6I zOo?40*NYF8UKGe3<1>fsa{zPt60$S@D8*NkL$~Hh${&mD8#=6`>RswZuvIlQEl~Ne z4o<5kb%63JXFKDEV8k6~Q@LZ);8163;UMVLgNo=ucRQmmtOR4EIQ6r(aMpjK1@pkg zvS-U?LAi(?^@egZ-)>3|N^0e7zC5C32g$QQ-q3QkezOFT9tCo6tI!^k7K&!TbEH5B zt7k1%41}d#b+UqworEZ#Y0}&YOU=4B2+({L4VjHx%G<-DWyPu z_eBF~#RB=ni#Vw?PyXvgD{bG!BA5HGN@(NXZ-sr_l&Rz9rqx5EELkY`N$^d)p9`DX z6wp!83`eEY0%5Cq=yM5xx>KM>bUl-mNb)4rOu2}oElbZ#)LIIBE~>;k6Bs0_+E8#IKaaSnIB|+5krjmnYlYLR#f7#fiHrCgEvqL@ z%##dV{5$z1z|A=Vb z0yUz$=ZT0m5t6S)bQ4+iN<_7!@Bc(}`5e1d8}jV6T{cTaL@^U6gQzoJMsyT`FC$v+ z|81c?nne_Ji_6IW5Ka9&zEWboiBsMJdz*rCkdUCqMT1zb0V;ikk2S)%4T##|oHd{S zF0rJ3q0pj+?`Cxp+$A?FkE>}T6qe1d6-a7R*$LeL2&6J!4W#!R{;rm_9k7Vl%$B7P zNyX8qd9_F&B;7C&)Ru#)TW&-xo79ykqY*;%J=}t{@E=KclR(S1?do&q0DA7+!|e%z za)#VcM$fX(J@p|(yLRO(3Wa&K0d>msYzjnG3p;g}pq3w|TMT8zOu0e3HztgH41K;r z-={PGkG?-BiC^j)OHNd^w(C15sO93eL%BHvtm>0GOs1*@G?|uBW9}q~lhU?gQQh|l zZKlr_kHKh{Hk@&(4x3b!#O3k|VF})=U4+KS1@E#a(pl&BpI8vS$g! zxU?V}CH)~^>J#57I6*NrC|}Ls8+EL))NCd{uVdk!E}&o@8;ljSKgH^^S)4gCAF19f z?(fLLLl)v~US%vXi5)Ger;$IIFqUm=(Fud(v_78ti+`b+6*J^VK$3Xal80^oc2Mag->*u}V>COl6wC{A) z(@(Nq_4L1~s;3{3#==^j4w$BTx-_S@r>-+pPrGDU`64HH8jQ-vPRu{>+~oi5X>ty~ z>BQo@<`VsXc=`}#)%KL8r1s(P)C0j$d)w}*|1|QH(_^ymZuj7vkj835M{fwRqIOmV zF3Qr$c<2->Sbw|=)&x!k(1uj00=(-byq(|)0_K}KGe1XyV^lhBz4R>zu9_rY9~UhBI9`4> zZc4z$KY$uINknBeO&jWj&}Td*g!=N9@!eX55Xa?-H8|*GLHTA}jlOzyh)WZ3#@~z% z3k_QoeZ9sJ#7LR?@<-+*EBA6`sgfoWuYUybxvqHgj$X-|p@6Z~1i5e4 zyFQnHM}5tBVS%kBi$>1K%A#-^h7$GRI4i&6$_C4cIicRAzX3B9_MxZMsW5oFoShTy z^z&Hi;xgr}OnFm|cgU6rFI}(?hU$aZHe=;)bEZe#{uR2+nW`?)(=#ANDMWz%0da>+ zdGd_DVm|9b0#Cu2H-pc2> zu^4UW7%Si9#zM5=nO1(@jn&uAdDF`8xv@!x7q_AELqM(`EF?Bn)or0^R=Gpn0vA^T zDR%s}`CpC0x6p}~8wVQtk%p&ls2bcizgZ(zWhzg+96Hy}@n^8jc|&e8zneav>M?J~ z^X3oHhf)3P82S4AUix#lP@geIZk{_XZV=TS$3WZvd~2}r8#O{_aoOb~hc3YDBRXuW z1LPsut{(kWL)6$>Tsqnq4Al&~N7d4B&s)`CpwLG@yNT9!y-pk!pB>&9Otu?>zem=R znn6-Me3uY=E5UB-O^eG>FL}X&R??ab^dBtK%MTWW`u=`#nFx%+lwJib@hf~ zkotCNCotGD&?OOPyM;VOBoNlSOIsMq2p?o~)%VBOUguPdgpN z%P0;cux_|KYN@Y_cr;-$cQYufhFf`t7i*!Fp@Dm`+ZpTUspEzKGvpaK;-r_y`~ilg%=*KaRF=Nz(E z@gHR6-o9)y&3L{^gs*~Lnto`OP|}8qMvUmQAfJ2RgS^H@rj zwv>y!X;oXlG3YGu&H+8xL02~=(H{oM4_6gNpMg%3*|uEMA{K^kJ}8R^5w;W!3lS{W zmO<+Cs>b5Yn0DIlpkmiRVO)(jW7N|E58&0Q=ohl|1C(I{={(ZvY0|z696|Z>lFsOi8^FD)d>HILwoYqul`U!jdags?TI< zy|DCEUwUCl??IfO_m_h=$IA~kdq#ix4d5yLNw-DaQ1&3FEj}xIB3w39Do{oA(xQ=` zCVP)188=nNrHLa}`}%z_wgTnQb3yg%5%EkvVXXSD-Z%g>i6+A^Slp0OsQwq_V0*JB z+c!40U;5bC(7mZ6h|N)DD-fMvPV~o=UqW4IwqeSYuSr(jA!C=I5^ei2^~v7z-u7H@ zqUKv%xCp&7DER1H8r#YZ#Fwnhhbncduel*9AbE;59#h7QAz{5uxPpYeVKankZ(a+I7^Jkdm3_joC^D{nI@u|e; z0X`Z>M@Q!9DDj8&SyLVo!urs@z@^7mP|Q-{kn(;v`TVZw+Ah7tdWiSDU3gXq3+k0y z1{U8QBFY6>@YF?*B3z+LR+6wDwT#YIp4gE2)LT!i z;^;EJBoSYkfDFA$g~Wc%%BMDD$AedNhIE^hOt*YGDa1($Zg4DR}3hgW3{VWVtEqISSgKg8h)wJu2PFCJA znswGqBwn7?nE7ZU6RmthH1i92trOB&lqzKh;s38QujbA4cWT1wMYOf6=u=b0Fj8?)V=lA$9NfUS zH%9Am9jyH8#w@nu-#3N)0mK#m;F=oMy%13y#`oxIIeejgwX&!Uyj2qx88ODjJiS({ zi(fKUtmpHapw*%6t$b4xHdY#t#_PngNa>T_JT8{Ct$R5E&C$SY)SKtTvi#hSAxvvm ziO2+c&Q*aG#uZ+8uWR#`czyu zoFa=C-*Lq<)R>pFWC4Z|`@k`{1S*pMY&Z{30<#!$^!K@#4h22hV85LS5JGAv@q3 zzFpJQTD-g!^I_W8;mXWOY&>^QU?GOOpMYgcoRzmuVD)|WG!+KWCc^eOXG2pfAD_UC z+WxUtzA^zD>%EPwyexr@4|^{h53;mn^bY1|_4`Jn2RN96Mtf^9@fq9>Iy_^qg z%f|a;?}DF>)l^3;R(VnitM)8$|Dp%O-Si#S=V}S-CHyd2{+Ob)N zsXJiF1T;RmJxeuAA;=HV$@Z+PwjWw{?tuGe*G4d*15)shC@b%Sif&LNv@6Gc(2d6h zBmC*xA@^srmH!T+&odBh1Hi1_T$e#ExFhS~v;O~(o;JX3dq*}<`nMkcz9aiAVef~~ z)s^()Jp?5df>#cG0kK*O=l?9I5f@%CX*f%gtd3$~)9KLj|27*94J z)70n2iYFq||26ie)&e(2!Q3QN|uLUgvKurzHm`2IKrDYYFK|1PY)=MoC% z{!qK!0T3~DVfAwV3kANayWQ%g;>f2VkohH}Y6SCJVyn$G9p2`bG^@e%2{FBFV`EL3 zqG1RJ430wZl-)2!jW53qe=w3zArMb+u2#rI2VHm*<19eeK2!XUS7vQsc1k%o2 zfLZmd{BAdv*}60sEs3YiG};zG2e!<~>S9J}vMFnUGS+xczcc!JS$CGMl~DPkI~$-~ zh7{}EgKacal|d$}zLi(@z?Tkm1s?kK@6i|xmYaMZrM-ATBM<&Qfo?$EAG{0vMOcN% z>3mvy$a`njm*a*-x@;uETp}nL0kY@iD6QvQqQ>z_*O&|6naWxOEXPhu-C?6b1Nhgq z5IPxz986^)(&RbZzb6Zq0_)4qDgpw#o`g}mgV2$53?L*B`~yJn2klsrI5Ry#3T_43 z&P+?@1nKW`VBQRrf1cA(PW&gZ@$Urd1Ib|Zt$SMUZ12ddwoPXx7|+Uim@%UlZgWoi zTKPx4*gJ*|Yar?yfVsF2^W#H%vmC?ggj(otO63=;<%#t~!&bfFJ>GxrJ}2B^_i)M($4L3Z{Pp-{ha-P*@KRp=`BtFx8(Dt5HD zNJ`4BXRwD7(yU7S4hmLUQ>uk_D<&1UQ{ZOnf=0rj{X*i7>PN_)Y0!^z4mL)l8LXI(1~ zOUHuwtPbYRbnIa3!;Nw2EJ%AD)-OqCrus$Ds&>*^YQ6#>jzo;;6J;iN?xy1b;8f`G z9me8}0kxUO5wmGD7KCvnv`V0Bl7sp9VXT$+Is4;nwKktdVva7*-5tp`Lr|)>5T` zb0;9E9b~`~I-CXirs&Cv)0AMN)r-YtO9*!!!5Z|g0~*b9uv?>k3l(4dB2GLK!kAZF zYAYWOu&C-#It>Wjv%_HAU#KSbiZ>CZn5cgzOkVm{OIObO`BQ(+;h#%@BVFVMsDHhp*n7 zEmTvsB7(g~A|Ed3tUPNZ3--KJrd2u*)q1F=d;qp8($gGz zoHJN(n^v{;ECwhntPvx8jf(n$G@&o(VsS|g;jd>P!9I1sFoT{J#RydmPxY!ElR9bW z@hX;b4c!KR#Q4EL`%P~xzLvqBFoQ!rhSEG3bnSK4QhMK!o*3YSk)CU&MiT8e7*M3- zOK3s-Hb?a<8yeX5E|P#h_wwZ1hgdh zkrtBBM7Qng6WxazdZNo$jbYs)PA>q<43*`7J(B!Uvho*WSd4Vv%46PO0WFGPs`_BS z0Sc90ZJ_NYri9!C{RRuy zjVEdKsfV8KYdT!Y1$i+8R*A6E;cp3c?Y_`o^LTqL(W~&hpcXcX;Z#4${w@Ep+g_Jxz=gQAH;*s#@Csj4kxmD0QAElcz*{p^t(`ret)D< zzt84K{cBh_A1$=f;4#@)R+f8U9-uej1oRhdqF$x9Hq8aw)oPS-_nFN>w}O*oA{{zL z+Ht~Z!D_aUuu>m!FRF49TvMrT*NR*j_56j^_DdEfdkZ??yFcgmGjTw=9DQ1M9JY&Y zPkG~UtYPC{pTexl=k``d15`tuh*;EYbr^aocO3I=^dk&IlQ<=@eJ@)14~VK%VNU^d z2xxI^3yt!fgV3B%HCnRByYm1{`A;0C z2g82a8M+m_Og_L9CNMmrA?t8tsx-sVHArGJBczI-@{$QGDE@6oW3^KI6(``Naq%ls zwx3o|iQ`m3kM=^4mMkHod&cifV4+gbQ|>yEO_5%I#&ajKzzFRN(l5TQ+wtEQI1h2m z!HohQ3m9#$h`;@me=(66U$atNZ5~vvpjivx}!2+J9UW-`gNS?I1dDaM>0v{@Yf z7`laD^fkQRk9ou-)<}|{@?Miz18KrjK4TKQmlow{&t@8Er3J|-=?{7R(M$xhFU-QJ zppXAP!im?;lq3Jxdo`ABYWGu$S@Ot$|Nh|zCbP&~11wW-q?Qu;I0VFvR8N!Ai98|u zrUPlaU$p=q{>X+uhtSz>Q3>y);*H8{wfX-92t7t+eQo}qfX_u{sHT4Pw;G}dJHi9% z#G!1nz4UzxZ#;cMBOj77lZAf7MRlYX*vujr`DLt7R0QKSk#_c9l(C=GeF#i=DV_3P=k38q{iLRcouE&Mxf`4@{i6! zozc+-zKyo%KYk~RHT2P=JFw2y-14pXn@3G$Jp-1J8gl_j;S0Uxc4gl@;>)M9$aWnO ziK5CG>e)#Gtxwgr*7NtE(S8&dad#(dg!MxSgCC`#P2RX@Yvk3w9vW-pm!d31Nneb` zpZNByDgRD_$((9TRHT1+v+1l}==W$3-*NFZ<M96Pj%&tuD_ znGd+be3mXfzRyR^$1A))-sidVS^q&lJa}O}rCJCZExM1O|0N7_^Exbc_Q2UulO%Dk zaz-~3Aj8cPZB=Lp#9xa4N(qG+R!vj>>4eIQ$PzN!mnY`3K&j#(AC}8n`d%P4*=|;! zz+N7jHo6jkIjo2n19(X;d*HGA+H=f;i`bzEiJmqtir^>mSVZ%Q0JHJQ!iVaaHDIdO zgo!>YqS$u<`%RLn?iatvXJJgry2HZ?SZ{ong?CxZEI~g$7H456PPo7M_=MG(!%mq_ zeC1yG?eDPrSUzjSmlUuFY2IDFyMU!hawUIJz&cYOIMy*I8}YWa%f%14+Y)B1*IPmU zCmR#!EUYBk{fIwJ3FzNArGdhGFJWfq^hz-aRJkcn?(pB1upYU?s%+Cjjy}Gm62Cf$ zd%NFWSeGK4XOfL!EWbmRmLgU6upZi!1E3X^>V^OZR{Ze5A9iu6YKYPt%9Q?hPz0eN z3=f}YGhqUbDe!A|N(gvimsAy}MA^lXa-^zo@O_3Kp9A0f+2C7>HcEAmB@f!ZYsd$Lft-GkwQk{RN${_0P;Ir2U@cG9tL!gV!jRcOsFc? zg`LHu4u7hNYvEW?{3QYsmFwsav5@8%NH=jQL)ag@?c1!6^tXmv-exl;-LKry%)+Ff zf8mYH?2gyv_raE<7gkr{5LI{PeIB(8lYi_~p07vE=PNjbTV`AfcL<+}M-PVxk8IeCuf$;q6Zi~b%Cj*I2K6kcB#?&s#m#)h239Y) zQs9J#y1Sx2%oru!UQ#aK=4BgLtWat{^LwyT1C$!S zCKWI6n#3sq2(;4R7ho*9{Mvl(0*i=O=@E%u8+qnNR$m%Y!Iy4idz?N-@0w!DmEpH| z(k8ZA8d$}zY{Fe*FtpR$j>#9#R?jLwRdA=xSouBL@s^ueT=dPh^yQ=j#v&*23mJNo zrKJOYxb?8)j+}Z6M@NfFvcoW8>euoWoAJ2*uQvSDX4b;%kE_sIV9icSMt&rM!C^gj z+Jfhu8^-ZwTi9!L%G!`(pIhtrye+Itz1aY5tS;N|%Z>#lsfGBqHU#&q<2Sdk@X)iF z9(Z2Oa@aYO`G9e|EBwK)EELf-{+v>BjfZ`}zSo^;?V+jS8>ca!2vgxOqdWahLmb7@ ztaHZZy;i`-Dm;EGi|D$1b*+Fu`RUbwKfECVKBG3@Re`k!{M~hY1C&W2SNX}UY>d?Q z2OhNz!z=d+@3D>bN%|UEX%#ta*Hohb2h@b~|8lQZ4aC(OKUIZk-0I`el1F~T*85EO1yiyY+IbxnaoD2s|Ak-vh_&(vB@8r$ zs_1WS^9M^<9PhcEg?L>of{{7+(vwiOB_4C-nnf&>Z`{rdhLnfy_)*HS@qEvA=HDw4 zkix%Lr-rEN(1=wz476$tZ6cHj-y*7o)Tn3~2gLbh*hiyNOodDd`+@uHVC`MR<<|S3B%CH}3VBj@IXpgFn z5Y2R(?z!`kGuXh$MuI=vpv;BtBK&|)3CyMy0tyVuN%CPYqYa5Ke=vjTrF!N3-x;iN z=uF6|(=&1UDLLQS;X2CAZ?O8_&tXCQ!8=T+p9GR}@EbmHCf__YHOz5o&t_q^`Nwg(We`J%z<*@>3 zFGP$seu0#OhB~$_vG+wjYA%cOx9BRtRGscvm<*-5Es8rI@(wde>l*P7-eG;UPG8&7 z*nKs2BV!uz=+*4Jp??`(AMn{2#jUH^(rBM^l*(dE7*td2d957zS|wrl8uE8U^|tBQ5H5PWIawp0|_5dF(qaSlkNm?R3m)kzxGAPUaJBwxbJx z#>s)jW$sdq2EVq5WmTE15_2Y6eSQmFvx|Ph_6!f)#o|2uh=Ns)%%oK==QJM%dfjxw zFE~y9$Sidfuh_)`awE!#3Hv8;av>TiM{{VzfJVBXB2{9cPZl)i#nrUfm$WFW&j%s2 z&<=7F-+ROz4vejUp7=|OV>KV%^ukZ70c$c}wE84L!jx;iLa`YO!q0X>BO+i22Ym0E zcn{!HReWocCD~|F@2aSSL&5j8kUvFjm!3d?^Q=C<<8BFBFcYJ_pXz&PKvN&yQCzN} zWBB9UtaZbCLi7wotv(AOLwRp9U+HE(WA&LPV91ZF&j)$oDgFWj6|X>%EmEzsySySj&yRn znJ=5nJ#Q)>mGI+x+59d^Ke}sjVp7{QiABY&;2Ub{>=T>;7V7E&BjmpT@{G*k^9khHQnb1P^7QM7kF)zY&RmTy|1 zo^CO$e+gEfqun^$&zgr9LVyhYoKoN1vlxdEADPTx%0uEI*T;3^1NLJFl-!NKwVx$R zFHF2-KVE~pXyW(xvp}Cy{^)dz18xjd^{}bLpV$ACB}i|Y_~1`j#_J9y56z;IWVe*R ztUd-}H=jXgko6KEG^#d;#(wH5;9}K)JYm2Y6^Cce_?4c{SyrFVRK#%=G1%&}wQF(U z0p`h8xjHx}_QM>PhCtmuqbdHv!WuBwj%T0;y0EiqcSge>EN5e#cYN!vp;H~*rf=Q( z=oL(Mb`lt=P{)9gir6RVnWxoP2TOIYqlRF@RNWL*P5Bis&_`oX#S-8hiT}WavN)zx z*V4vl*2SZiU*=G#izWfHt~FiqEAB(C@o5dOyOO1*o;an^AU!l@-Eq|N%M69OgQ!5K zA-^IN8fTa4HUOa$JoGc_`DLMnx>cw^<3_sVS3HNtcTQ>e2P;_vQ*Yq#hzlBf3hu;0 zU9!sEN9Dc>?k=dJ4I}K%BfDwi*PO_1M;_pXa$N&x^H{|iH!m$mu9tDSbV)tx~JmNuTOkaqyizuT!3Da5BlWX8jqTXGUsiN#9$}~~-6=i=> z4ix2JQLYl&wu;i%%R`fEjnv>`jr>EPu8H!3C`&}SUzDoA8c|;=%C|&0QIsP@SuV>uR;A9M$+57{!P}|@Vu}#-J8F2 zlhreP{0bUk;(es4h5X)4G=t`jU1KqIy={CWho9nGE11#a$V&WX1MYowdr(2cp>k4^ zAAeB6T%`qtYi>cbPa%)HMc>{fFo0*=VvRlbuA?tFq-2-}493)zrnFqh-@nEB@{(J) z;}~DTx)kS~V-FCNhQ*yE=^LpyPA4sOEZ*cIU6J_GKq;#I7_B;;Oxz{qVb;|cZE%JS z-UOV3q|pqv!3ltA(Mki%r>00tI8=7vdxIo{LmuI7B}>U`yd-~z^4%)bf~R?bSYZQ= zYrH{B`?wY{!v-389#P9{Q!A>eiGET-ZuTdtskA|IfGJrv_!lx2pNTfO40SSfj1Asu zqi5LQMK*Yt4IX2I2iss1V3JR>!Kl(7pPM4hC9j|JtN`*FWQxS1HGz`5a~!ci%6*^R z!HhOi9jy&AHl8$P#v9YYLRVs4gxKPrW-T}<{)zZdyo*QzTF3Cv_!{zSf+=|W?Yc!| zGi;!dhlWt_YEvt!shfzJ_GxWd8)#e;MxwQe6?P)eBWl`#+M+fPeHKo#wW$?0upTdp zko@`H2x%1m*eL0}M(0e=o-(P)8BUONeL|$9a1%B<$)caSvEKg zAieqF#1pdNO^t8X3Z5F zyc96?={XxrgOB)1Nc@#~MRQzjp+C=vmAb{3)Ydr52HQ1Gw83_b8GvhO+!O^52n7%N zuZe@cleP8v+hDssXB%wSrvY3;Ut%-pBNXZ5o0>_DyiTbKx5FI+KAQlm(MxO&UV=oZ z^O}SAbnV9T0IR%W*b@Za#-5=!Rlf1wOoVg4A9sq78aAs?(b?5#m5f%!-*%gruUEC$ z>k(2ccQ<0J4UU%{tc*3RX&_}0!jboCAw~1N7D#~YrK(<<&1g2-1Xwkz$QS9dmq6HM z#!SrB&3dZ=v^CgW9KWju1QDNP}B8zUR%k(*`^Ds_}+`^)$ol? zo$Z=syKc8#8E1u(UvWiSsmw9r$#*KV|M%)O&34VQUAKQP6`xI#CTq2!TG6Oa-A!J? z+E84{3LhStDmn9cSi&M~q&V9((RNLtkLMK3RRxx^^|m;P-y?NE6+%gg6onrI5pcVpe#UjydpxYG2D_gNC(wK_g<2& z6%h8nxEgiBk%0ZLrU_6|OfE3*zWtK>#kF?0dVR-J)3La>T<^>&?#bu&mfE{SLy$i7 zJTrp0W28}++-pw>|F6TvefmnRwPVWtYPa+M1zeq08yI&VEOqi~*(lz~miqW*`DXZ} zz3s^~3A_1)bSa!CPLW*r;lWaSex8aZBc(L{JYDkUJBCQD9f|Xah9AoY=cY_xqo9r&23+tEDTuqjiLrEUgk<37hDip4sunxM zk}*;MKRN+YX_=6kGzK+T~7Mft*BYxu|gxiT!aVY{;O9iBa%u zn&j*o&_NgJ72(R7Y4ll+woaVI`1o`w&=VaT864ryBC9)?zc)f^&f|t4fJjz7KQ|gd zPo6Hhx27O(wFc{{p0mvZAVcO3rLDbMthmNeg1= zNElknHBntr9fyyzYdIW>4PAyJjfzbu=08C3VW@@QnJ#_FI}C*tCDY*MzHE#Z-AH(t zoFhFE@j3RK;u=+o%Xxex*SxnyeD+HB__}e34-yo^bqG|XW}wz`JVLV}8^t`zsFD$q zOSCNnoopc>OW%AUY5owdMomFA2Or6`;%#mohjf2uhU6YZT>F9EuX25k>IuQ+Zsx~l zNRtFZTWhtN*HG<-kL0>ns0rt~*U6MA7({8~5#qQBQXiTCA}Fbs&%_ADz-h>Lr_(-T zESg0+d-J0djEXnm30Qsk4>Khfk<$i=*Pnvf;6b|N#=WM%aWP+t0rYUDG`+rgr<+Dq1x;xtY`2cU3j?wCfh!Nff-3T3ndUphxJ z)z1o%G>h>WMil&o;qwy5@6C}0wZv%yemhg_4wygF_%B2qPi|Z~Ycw8Y7~sk7_zzVe z`Y>A3%!rXRB^Ew^t`rzfc-Wv>)L7E&!{-5PqrW+*|A0^3<^0H8sjaVFKBB3E4=Y4) z;gJF1XwmWiY47~Qqp0pZeC8yST%o!c#NeecLe%s&=+cH773(4qB8IrNsf3EM*rWuF zxImQ1uies!3q)Oek&T+GlVrEqkWCo5C|jF)w>(kTJ}6`1K1NLGs%Y~_fK@?aMeTc% z!Jz;4-{#@N`(l%OEh zC@Bo>Sud|vqwPy#COY{J$0Bs#tbB&kLbHAH%Fxo)a?;mrdS(`*xrja?Gu}Esy;^Py zm93N0LhF1C-nw;MM8|y6sR}sclNZfY4fCk!WB$gEmtrKZRw~VPrm+`rKUEEHR5gR5>ox0ozG4DV(!_04i?nap!z#IZV9#h_FwMUp)`~7eB#+>~Ych1rW;u1x z+`U;&S7vt$DcYdhzeRrUB43(k43_DdaPT_qg)gGu$5N!=xQjJ00q=y5qP-tUVZtl< za#{`E10O|Rwl!em6t?|@ZQ-I^P2`g9hp(cr$`cwq&u=f@g4vhyOR*=&$a$$Ieu37q zu@}C8vQ!Yvyi5}TVsxl}Mz@d{okvqP@dQsNF^(T-;ww~wcja^7Po)52-0%yOBAv%8 zh?OWsdJvXeL0P2xui%%0BYa1>gcT;Ybbp8%DNOL&(93_Ui4z1_Z~;G>94E#PD|y?S zg*V`PC>JkyT=jb>jMuM*s7h@MTX;UUsTy!C`JiIe)36D}eFT}95j-pW@KJ)GdT#jX zG?`wfiDtAHZ^2p@6`}wWzJnH$Q5u%c)P$RDz3>@Sb(|P@6rDIiKG$nvJ}U1g21Zes zJQL7&l>gGl2%4pdX=owd1+$AZQHOWKJt&tJq~JL>Xrh309hRX&(!KEiQJhTEaM^6e z6X`*?2L-8w1^shK$D2^xsEPWc(kDEa=a837EvU_9cd)So&P2Vc5^xhr;thBb>39b} zL|lVL2Cut?!KKpS`>2Xt=J**W05m|JDY&7G zc*-(;n7^So8>Hcne@=Jd-SCxLHBm&DI_Iu1_+zAV>Fe0UDH)x>d!c(X`;u$&AUudhlb(jFw$M7RgbjFEJtLeL?iCiV zq6u1tNW<(grP=AsEJ3s;O*QkJ)AbbfGk$(za`xF($^C_~}ic-XwPx(0AHfo}a zcR2e0aF`*JN_gRxXV@ip-O$7(%~S|)!mrQ?3f5aRaWCp7JqS01Ido}#0)C`CF(SfQ z71a>qg3HlZD(Z*Nq7ok~5}3T-G0@n+4R@jd=@y*YstFI?4Ie~nDZqfUqKpr`7xtk> zVjOL3hZcTH%iy`&8Fa)r;SuFkrQ4|(GN^Sa)S;HRigRpL2K zyoq{LZ#~cRCZA{jm(fLO7Jj>%0m6y|lyP1rX}u1YA`9<_dr%iX1q*)9fTdtJ45I)( z0mr_;E}<|dd;%rup#=OCnZ!8$z(pXcX3QBrjM>F5U_tzmLeW-L037ooUJXpR0QvD= zcmVAp(=^QcKL#n8y5L6Ch&SMVG(+tgI3>ZU8t;O?L5I~Cf@A)~{y$7%PRu4WfH&ZA zly{n%?%`Fdn~d(MTC z;)1=%z^CEGDJnsWozU@)CUgpR!?t%hmrzmh9t}Rspg!$m1I+UGIc2KC;D;ZODFwLx zu8DOhNv1)#ypOXU8JRHiL(+-yz~3LCaJ-|RL5J!o*aII&UJ5heeIHQ?T5Ntyg+5_7 zlaCWV=R3&;Dx*(1{Lle5NWt%(qIMLl!vM-u8Np2hbUQu)Cw|6JBRR3ahfq0ZHv=yE z2LqGzAp9KpN!LH;_5L&ioOC~Y7HuWnm%wCvL5)-poR7NjUbs_}yg}zgBzYsr$4nOK zo)J>aA1y_{ia%G15UR!};Jowrf)a1Z1gr`xQJe^~tcx;Ti{$s&p zA@h881zv{_q8hvb=VVLKi1)%VW622bf@R~R=*6et>hZh-rU3mS(*BdY@a_pxy!Sn} z#d{_)wS(e#3m!uWyqLuPpEya1BnwWs7+Lt?$%Tx;iHF~jUcuypVmn%kPr!U#iUPbF zPQRGXGVnpT4>jOZFk>>~2k(F-$iRDG<`j0Rj|C^@EtJ6Pxl&w-qIf^FE}?QNh8y(C zQ5^4u+fffb0bjk0IK0l6=a-=ro=;K5i>ME8!ByX9m#KJo8lAv9m1P8x>S;SDIM z;^9Wr_?--&FffNvSj|*S8=n18vmjUhkV=rQI3Kz2eC{faqkb|Pp3%q{ob3n~kWP%^ zODJ-5(RT#V!72;j+LF7{L@Hun>pMV>G!W0jWGfDU`ucOnfNWr_Vqt>i2 zpma%5Pr43oMtkudxDIvWgD`;(<1P3;>c^+ytuv+Q!h4~C4&Y7rGU`?F@S^M4|BWm- zG1F(!MR-3PRV0N+rNc#NE8YutpfElGuf2g&DxMG4L^~>1>F^U&jCalE_(o%Cj0s;v z`S=vvI7fWOJC~Cxu zxl&9)VY~}2K{31+HlcdF3ExD!vV0VbnS2wiR2#r{REM|VNmPU9JS(n4-FP>A9PL#x zaQr;F7w?1*pk90seuld6dMSe!t;PFc>Sh}GAC%>0mfLp=uSP6*e?~wV0}Y>mp>p=C z%4mTUEqBn>q;skim!Jx~do{g+ylPu`KdQy63G-HT;0p${A}ariLSg0>8uleGRImV9 zc%KWi1QmToW-x^6@CNKg4qmm?r2eRSx(=`A_UE8RyqeM3 zR6Lx6dR08EK{GO_D2zQUOT=h(R$}38q;n{$$=6YjFxQJ$oQx{eu?}ld6t8AHO|+Fm zR83fZf$BzPs2RBhBYmUSXRG-!p|2Zafz8c!`dXXSHh*EW)aLaze`s@}&2#L0j@j(7 z*=4hNh!aG;ov^~@LYuSKWrwKZy6l1nhH6%7Gv|T96}S6-YbWfq*=6&9&0{t*R}W<{ z!R8E`3vI5j8MGO*`LfORc0K`{gZ;$^qfm+BT^b;*aMf^Hnl$fv<;`Z+`rm_=-wCh>=f4N-;Q0F&25qQ(pIn`Fy;xbZf`6fWi_a22 z*e8oarIyU$0SY%;d>(9Cd~on9i+c(WTC(oKX(I@&u3RR>n;DDmTq*i97T4UnRD^DS zgOBc;-;fhS$v4R6`M=2g(6PU;)rW7$y`yd#QB%3%o~6qc*Hnfs-Y>5iJ6q1FteJJ+ ziWQa1YRalsR4%T#u_E;Derbh_{jzBH&I7VELw!Es-_|2*LYE(u*^fCh@wv*6-2G|~ z_o}G%*)ty>^UWJpzP;Qw?wjjh`__##uZ6hrWm4v5?tA(AzLY6i=-fl{n#^aj#md1% zRaE0{FLxJZ`V6k&==nf { + document.getElementById("colorModal").style.display = "block"; + window.selectColor = (color) => { + document.getElementById("colorModal").style.display = "none"; + resolve(color); + }; + }); } }; diff --git a/JoshHeaps.Net/wwwroot/js/ChessScripts/chessMain.js b/JoshHeaps.Net/wwwroot/js/ChessScripts/chessMain.js index 826816f..0f48029 100644 --- a/JoshHeaps.Net/wwwroot/js/ChessScripts/chessMain.js +++ b/JoshHeaps.Net/wwwroot/js/ChessScripts/chessMain.js @@ -91,7 +91,8 @@ async function startCPUGame() { try { const difficulty = await ChessModals.promptDifficulty(); - const gameData = await ChessAPI.createCPUGame(difficulty); + const color = await ChessModals.promptColor(); + const gameData = await ChessAPI.createCPUGame(difficulty, color); GameState.setGameInfo(gameData.gameId, gameData.id, gameData.isWhite); GameState.clearPreviousMove(); diff --git a/native/chess_engine/src/chess_engine.cpp b/native/chess_engine/src/chess_engine.cpp index 740531e..a674594 100644 --- a/native/chess_engine/src/chess_engine.cpp +++ b/native/chess_engine/src/chess_engine.cpp @@ -208,14 +208,32 @@ static int evaluatePawn(const chess::Position& pos, const chess::Color c, const return score; } +static int piece_value(chess::PieceType pt) { + switch (pt) { + case chess::PAWN: return 100; + case chess::KNIGHT: return 320; + case chess::BISHOP: return 330; + case chess::ROOK: return 500; + case chess::QUEEN: return 900; + default: return 0; + } +} + static int castleIncentive(const chess::Position& pos, chess::Color c) { - if (!pos.pieces(chess::QUEEN)) - return 0; + chess::Bitboard pcs = pos.pieces(); + int total = 0; + while (pcs) { + chess::Square s = chess::pop_lsb(pcs); + chess::Piece pc = pos.piece_on(s); + chess::Color c = chess::color_of(pc); + total += piece_value(chess::type_of(pc)); + } + chess::Square k = pos.king_square(c); bool castled = (c == chess::WHITE) ? (k == chess::G1 || k == chess::C1) : (k == chess::G8 || k == chess::C8); - return castled ? 600 : 0; + return castled ? (total / 10) : 0; } static int evaluatePiece(const chess::Position& pos, const chess::Square& s, const chess::Piece& pc, const chess::Color& c) { @@ -274,17 +292,6 @@ static int evaluate_stm(const chess::Position& pos, bool whiteToMove) { static int score_to_tt(int s, int ply) { return s >= MATE_BOUND ? s + ply : s <= -MATE_BOUND ? s - ply : s; } static int score_from_tt(int s, int ply) { return s >= MATE_BOUND ? s - ply : s <= -MATE_BOUND ? s + ply : s; } -static int piece_value(chess::PieceType pt) { - switch (pt) { - case chess::PAWN: return 100; - case chess::KNIGHT: return 320; - case chess::BISHOP: return 330; - case chess::ROOK: return 500; - case chess::QUEEN: return 900; - default: return 0; - } -} - /* Heuristic for searching the most promising moves first, which makes alpha-beta prune far * more. Bands, highest first: the TT best move, then captures by MVV-LVA (most valuable * victim, least valuable attacker), then the two killer moves for this ply (quiet moves that From f09ca60315f5f8632385a67bb1fd13dd7f5bc435 Mon Sep 17 00:00:00 2001 From: Josh-Heaps Date: Fri, 12 Jun 2026 18:02:09 -0600 Subject: [PATCH 2/2] foo bar --- .gitignore | 3 + JoshHeaps.Net/Controllers/ChessController.cs | 198 +++++++++- JoshHeaps.Net/Models/GameState.cs | 18 +- .../Models/LearnedWeightsSnapshot.cs | 9 + JoshHeaps.Net/Pages/Watch.cshtml | 36 +- JoshHeaps.Net/Pages/Weights.cshtml | 44 +++ JoshHeaps.Net/Pages/Weights.cshtml.cs | 11 + JoshHeaps.Net/Program.cs | 1 + JoshHeaps.Net/Resources/chess_engine.dll | Bin 192512 -> 249344 bytes .../Implementations/ChessEngineFactory.cs | 16 +- .../ComputerMoveOrchestrator.cs | 51 ++- .../Implementations/CustomChessEngine.cs | 49 ++- .../Implementations/LearnedWeightsStore.cs | 64 ++++ .../Interfaces/IChessEngineFactory.cs | 15 +- .../Interfaces/IComputerMoveOrchestrator.cs | 18 +- .../Interfaces/ILearnedWeightsStore.cs | 33 ++ JoshHeaps.Net/wwwroot/css/chess/spectate.css | 20 +- JoshHeaps.Net/wwwroot/css/chess/weights.css | 187 ++++++++++ .../wwwroot/js/ChessScripts/Spectate.js | 25 +- .../wwwroot/js/ChessScripts/Weights.js | 158 ++++++++ native/chess_engine/include/chess_engine.h | 29 ++ native/chess_engine/src/chess_engine.cpp | 346 +++++++++++++++++- native/chess_engine/src/position.h | 16 + 23 files changed, 1286 insertions(+), 61 deletions(-) create mode 100644 JoshHeaps.Net/Models/LearnedWeightsSnapshot.cs create mode 100644 JoshHeaps.Net/Pages/Weights.cshtml create mode 100644 JoshHeaps.Net/Pages/Weights.cshtml.cs create mode 100644 JoshHeaps.Net/Services/Implementations/LearnedWeightsStore.cs create mode 100644 JoshHeaps.Net/Services/Interfaces/ILearnedWeightsStore.cs create mode 100644 JoshHeaps.Net/wwwroot/css/chess/weights.css create mode 100644 JoshHeaps.Net/wwwroot/js/ChessScripts/Weights.js diff --git a/.gitignore b/.gitignore index e8379fe..6a4cfa1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +# Learned chess engine weights (runtime training output, not source) +chess-data/ + # User-specific files *.rsuser *.suo diff --git a/JoshHeaps.Net/Controllers/ChessController.cs b/JoshHeaps.Net/Controllers/ChessController.cs index 46e38e9..350d8f2 100644 --- a/JoshHeaps.Net/Controllers/ChessController.cs +++ b/JoshHeaps.Net/Controllers/ChessController.cs @@ -15,6 +15,7 @@ public class ChessController( IBackgroundTaskQueue queue, IChessEngineFactory engineFactory, IComputerMoveOrchestrator orchestrator, + ILearnedWeightsStore weightsStore, IHubContext chessHub) : ControllerBase { ///

@@ -30,6 +31,10 @@ public class ChessController( private static readonly TimeSpan _selfPlayMoveDelay = TimeSpan.FromSeconds(1); private static readonly TimeSpan _selfPlayResultTimeout = TimeSpan.FromSeconds(30); + // Plies of random legal moves at the start of a training game, so self-play and + // engine-vs-engine games explore different lines instead of replaying one game. + private const int _openingRandomPlies = 4; + /// /// Create a new chess game and store it in-memory. /// @@ -52,22 +57,24 @@ public class ChessController( _ => Random.Shared.Next(2) == 0, }; - gameState.Computer = engineFactory.Create(difficulty); + var computer = engineFactory.Create(difficulty); if (isWhite) { gameState.WhitePlayerId = playerId; gameState.BlackPlayerId = computerId; + gameState.BlackComputer = computer; } else { gameState.WhitePlayerId = computerId; gameState.BlackPlayerId = playerId; + gameState.WhiteComputer = computer; queue.Queue(async () => { // Give user's browser time to connect to signalR and such. await Task.Delay(TimeSpan.FromSeconds(1)); - await orchestrator.PlayAsync(gameState, gameState.Computer!); + await orchestrator.PlayAsync(gameState); }); } @@ -82,13 +89,22 @@ public class ChessController( } /// - /// Create a game the computer plays against itself and auto-play it move by move, - /// broadcasting each move so it can be watched on the spectator page. + /// Create a computer-vs-computer game and auto-play it move by move, broadcasting each + /// move so it can be watched on the spectator page. Each side's engine and skill can be + /// chosen independently; when the learned engine plays, the game also trains it. /// [HttpGet("watch/cpu")] [HttpGet("watch/cpu/{difficulty}")] - public ActionResult CreateSelfPlayGame(int difficulty = 4) + public ActionResult CreateSelfPlayGame( + int difficulty = 4, + string whiteEngine = "custom", + string blackEngine = "custom", + int? whiteSkill = null, + int? blackSkill = null) { + var whiteKind = ParseEngineKind(whiteEngine); + var blackKind = ParseEngineKind(blackEngine); + var gameState = chessService.CreateNewGame(); _games[gameState.GameId] = gameState; @@ -98,7 +114,14 @@ public class ChessController( gameState.BlackJoined = true; gameState.WhitePlayerId = Guid.NewGuid(); gameState.BlackPlayerId = Guid.NewGuid(); - gameState.Computer = engineFactory.Create(difficulty); + gameState.WhiteEngineKind = whiteKind; + gameState.BlackEngineKind = blackKind; + gameState.WhiteComputer = engineFactory.Create(whiteSkill ?? difficulty, whiteKind); + gameState.BlackComputer = engineFactory.Create(blackSkill ?? difficulty, blackKind); + + // When the learned engine is playing, attach a trainer so the outcome can train it. + if (whiteKind == ChessEngineKind.CustomLearned || blackKind == ChessEngineKind.CustomLearned) + gameState.Trainer = weightsStore.CreateTrainer(); ScheduleRemoveGame(gameState.GameId, _computerGameTimeout); StartSelfPlay(gameState); @@ -106,6 +129,13 @@ public class ChessController( return Ok(new { gameState.GameId }); } + private static ChessEngineKind ParseEngineKind(string value) => value.ToLowerInvariant() switch + { + "stockfish" => ChessEngineKind.Stockfish, + "customlearned" or "learned" => ChessEngineKind.CustomLearned, + _ => ChessEngineKind.Custom + }; + /// /// Joins the "pool" of chess players. /// Test code expects to receive a GUID for the player @@ -163,6 +193,8 @@ public class ChessController( g.GameId, g.IsVsComputer, g.IsComputerVsComputer, + WhiteEngine = g.WhiteEngineKind.ToString(), + BlackEngine = g.BlackEngineKind.ToString(), CurrentPlayer = g.CurrentPlayer.ToString(), MoveCount = g.MoveHistory.Count, g.IsCheck @@ -172,6 +204,26 @@ public class ChessController( return Ok(activeGames); } + /// + /// The learned engine's piece-square bonus table, for the weights-visualization page: + /// one 64-entry array per piece type (Pawn..King), white-relative (A1=0 .. H8=63). + /// + [HttpGet("weights")] + public ActionResult GetLearnedWeights() + { + var names = new[] { "Pawn", "Knight", "Bishop", "Rook", "Queen", "King" }; + var featureNames = new[] { "Mobility N", "Mobility B", "Mobility R", "Mobility Q", "Passed", "Isolated", "Doubled", "King safety" }; + + var snapshot = weightsStore.Snapshot(); + + return Ok(new + { + mg = snapshot.Mg.Select((squares, i) => new { name = names[i], squares }), + eg = snapshot.Eg.Select((squares, i) => new { name = names[i], squares }), + features = snapshot.Features.Select((value, i) => new { name = featureNames[i], value }) + }); + } + /// /// Get the state of an existing game by ID. /// @@ -232,8 +284,12 @@ public class ChessController( await chessHub.Clients.Group(gameState.GameId.ToString()) .SendAsync("ReceiveMoveUpdate", gameState.GameId.ToString(), moveDto, result, state); - if (!isGameOver && gameState.IsVsComputer && gameState.Computer is not null) - queue.Queue(() => orchestrator.PlayAsync(gameState, gameState.Computer!)); + var sideToMoveEngine = gameState.CurrentPlayer == PieceColor.White + ? gameState.WhiteComputer + : gameState.BlackComputer; + + if (!isGameOver && gameState.IsVsComputer && sideToMoveEngine is not null) + queue.Queue(() => orchestrator.PlayAsync(gameState)); return Ok(new { result, state }); } @@ -314,8 +370,9 @@ public class ChessController( } /// - /// Drives a computer-vs-computer game: keeps asking the engine for the side-to-move's - /// move (which applies and broadcasts it) until the game ends or is removed. + /// Drives a computer-vs-computer game: keeps asking the side-to-move's engine for its + /// move (which applies and broadcasts it) until the game ends or is removed. Training + /// games get a randomized opening and feed their result back into the learned weights. /// private void StartSelfPlay(GameState gameState) { @@ -324,15 +381,19 @@ public class ChessController( // Give spectators a moment to join the SignalR group before the first move. await Task.Delay(TimeSpan.FromSeconds(1)); - while (_games.ContainsKey(gameState.GameId) - && !gameState.IsCheckmate - && !gameState.IsStalemate - && !gameState.IsThreefoldRepetition - && !gameState.IsForfeited) + // Training games open with random moves so they don't replay the same line. + if (gameState.Trainer != nint.Zero) + for (int i = 0; i < _openingRandomPlies && _games.ContainsKey(gameState.GameId) && !IsGameOver(gameState); i++) + { + await orchestrator.PlayRandomMoveAsync(gameState); + await Task.Delay(_selfPlayMoveDelay); + } + + while (_games.ContainsKey(gameState.GameId) && !IsGameOver(gameState)) { try { - await orchestrator.PlayAsync(gameState, gameState.Computer!); + await orchestrator.PlayAsync(gameState); } catch (Exception ex) { @@ -343,12 +404,100 @@ public class ChessController( await Task.Delay(_selfPlayMoveDelay); } + ApplyLearning(gameState); + // Leave the finished game in place briefly so spectators can see the result. if (_games.ContainsKey(gameState.GameId)) ScheduleRemoveGame(gameState.GameId, _selfPlayResultTimeout); }); } + private static bool IsGameOver(GameState gameState) => + gameState.IsCheckmate || gameState.IsStalemate || gameState.IsThreefoldRepetition || gameState.IsForfeited; + + /// + /// Feeds a finished training game's result into the learned weights, then frees the + /// trainer. Both sides teach the table — the winner's squares/features up, the loser's + /// down. A checkmate is a full-strength result; a material-imbalance draw is a half- + /// strength win for the lower-material side (holding a draw while down material is a + /// success; only drawing while up is a failure). A balanced draw, forfeit, or unfinished + /// game teaches nothing (but the trainer is still freed). + /// + private void ApplyLearning(GameState gameState) + { + if (gameState.Trainer == nint.Zero) + return; + + if (TryDetermineOutcome(gameState, out var winner, out var weight)) + weightsStore.ApplyResult(gameState.Trainer, winner, weight); + + weightsStore.DestroyTrainer(gameState.Trainer); + gameState.Trainer = nint.Zero; + } + + /// + /// Determines the trainable outcome of a finished game: the winning color and the reward + /// weight. Returns false when the game teaches nothing (balanced draw, forfeit, unfinished). + /// + private static bool TryDetermineOutcome(GameState gameState, out PieceColor winner, out double weight) + { + winner = PieceColor.White; + weight = 1.0; + + if (gameState.IsCheckmate) + { + // The side to move is the mated one, so the winner is the other color. + winner = gameState.CurrentPlayer == PieceColor.White ? PieceColor.Black : PieceColor.White; + return true; + } + + if (gameState.IsStalemate || gameState.IsThreefoldRepetition) + { + var (white, black) = MaterialCounts(gameState); + + if (white == black) + return false; // a balanced draw carries no signal + + winner = white < black ? PieceColor.White : PieceColor.Black; + weight = 0.5; + return true; + } + + return false; // forfeit / unfinished + } + + /// Total non-king material per side (P=1, N=B=3, R=5, Q=9), for draw adjudication. + private static (int white, int black) MaterialCounts(GameState gameState) + { + int white = 0, black = 0; + + for (int row = 0; row < 8; row++) + for (int col = 0; col < 8; col++) + { + var piece = gameState.Board[row, col]; + + if (piece is null) + continue; + + int value = piece.Type switch + { + PieceType.Pawn => 1, + PieceType.Knight => 3, + PieceType.Bishop => 3, + PieceType.Rook => 5, + PieceType.Queen => 9, + _ => 0 + }; + + if (piece.Color == PieceColor.White) + white += value; + else + black += value; + } + + return (white, black); + } + private static void ScheduleRemoveGame(Guid id, TimeSpan delay) { if (_gameRemovalCancellationTokens.TryRemove(id, out var oldCts)) @@ -366,8 +515,21 @@ public class ChessController( { await Task.Delay(delay, cts.Token); - if (_games.TryGetValue(id, out var game) && game.Computer is not null) - await game.Computer.DisposeAsync(); + if (_games.TryGetValue(id, out var game)) + { + if (game.WhiteComputer is not null) + await game.WhiteComputer.DisposeAsync(); + if (game.BlackComputer is not null) + await game.BlackComputer.DisposeAsync(); + + // Free the trainer if the game never reached ApplyLearning (e.g. timed out). + // The native ABI is shared via CustomChessEngine's import resolver. + if (game.Trainer != nint.Zero) + { + CustomChessEngine.NativeMethods.trainer_destroy(game.Trainer); + game.Trainer = nint.Zero; + } + } _games.Remove(id, out _); } diff --git a/JoshHeaps.Net/Models/GameState.cs b/JoshHeaps.Net/Models/GameState.cs index c3c86da..7ad3e13 100644 --- a/JoshHeaps.Net/Models/GameState.cs +++ b/JoshHeaps.Net/Models/GameState.cs @@ -1,4 +1,5 @@ -using JoshHeaps.Net.Services.Interfaces; +using JoshHeaps.Net.Services.Implementations; +using JoshHeaps.Net.Services.Interfaces; namespace JoshHeaps.Net.Models; @@ -59,7 +60,20 @@ public class GameState public bool IsVsComputer { get; set; } = false; public bool IsComputerVsComputer { get; set; } = false; - public IChessEngine? Computer { get; set; } + // The engine playing each side (null for a human). In a human-vs-computer game only the + // computer's side is set; the orchestrator picks the engine for whoever is to move. + public IChessEngine? WhiteComputer { get; set; } + public IChessEngine? BlackComputer { get; set; } + + // Which engine implementation each side uses — lets game-over handling know which side(s) + // are the learning engine, and lets spectators see who is playing. + public ChessEngineKind WhiteEngineKind { get; set; } + public ChessEngineKind BlackEngineKind { get; set; } + + // Native per-game training accumulator (nint.Zero when this game isn't training the + // learned engine). The engine records each played position into it and applies the + // result on game over. + public nint Trainer { get; set; } // optional: convenience public bool IsOpen => !WhiteJoined || !BlackJoined; diff --git a/JoshHeaps.Net/Models/LearnedWeightsSnapshot.cs b/JoshHeaps.Net/Models/LearnedWeightsSnapshot.cs new file mode 100644 index 0000000..a234c64 --- /dev/null +++ b/JoshHeaps.Net/Models/LearnedWeightsSnapshot.cs @@ -0,0 +1,9 @@ +namespace JoshHeaps.Net.Models; + +/// +/// A copy of the learned engine's weights for display: midgame and endgame piece-square +/// tables (one 64-entry array per piece, in canonical Pawn..King order, white-relative +/// A1=0..H8=63) plus the feature weights (mobility N/B/R/Q, passed, isolated, doubled, +/// king safety). +/// +public sealed record LearnedWeightsSnapshot(int[][] Mg, int[][] Eg, int[] Features); diff --git a/JoshHeaps.Net/Pages/Watch.cshtml b/JoshHeaps.Net/Pages/Watch.cshtml index 80635e6..3e04add 100644 --- a/JoshHeaps.Net/Pages/Watch.cshtml +++ b/JoshHeaps.Net/Pages/Watch.cshtml @@ -9,16 +9,38 @@

Live Chess

Loading games…

- - +
+ White + + +
+
+ Black + + +
← Play a game + View learned weights →
diff --git a/JoshHeaps.Net/Pages/Weights.cshtml b/JoshHeaps.Net/Pages/Weights.cshtml new file mode 100644 index 0000000..c784c51 --- /dev/null +++ b/JoshHeaps.Net/Pages/Weights.cshtml @@ -0,0 +1,44 @@ +@page +@model JoshHeaps.Net.Pages.WeightsModel +@{ + Layout = "_Layout"; + ViewData["Title"] = "Learned Weights"; +} + +
+

Learned Piece-Square Weights

+

Where the learned engine thinks each piece belongs.

+
+
+ low + + high +
+ +
+ ← Watch / train +
+ +
+

Feature weights

+

Learned value of each contextual feature (per normalized unit). Mobility is per piece type; passed pawns are endgame-weighted, king safety midgame-weighted.

+
+
+ +
+

Midgame tables

+
+
+ +
+

Endgame tables

+
+
+ +@section Scripts { + +} + +@section Styles { + +} diff --git a/JoshHeaps.Net/Pages/Weights.cshtml.cs b/JoshHeaps.Net/Pages/Weights.cshtml.cs new file mode 100644 index 0000000..baed090 --- /dev/null +++ b/JoshHeaps.Net/Pages/Weights.cshtml.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace JoshHeaps.Net.Pages +{ + public class WeightsModel : PageModel + { + public void OnGet() + { + } + } +} diff --git a/JoshHeaps.Net/Program.cs b/JoshHeaps.Net/Program.cs index 4c0e0c8..0657f4e 100644 --- a/JoshHeaps.Net/Program.cs +++ b/JoshHeaps.Net/Program.cs @@ -23,6 +23,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.Configure(configuration.GetSection(ChessEngineOptions.SectionName)); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/JoshHeaps.Net/Resources/chess_engine.dll b/JoshHeaps.Net/Resources/chess_engine.dll index 190af57982bcd281d2a36570973b1f68c9ce2724..992d57e623f320164f8be16f4543ebcd864faa13 100644 GIT binary patch literal 249344 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¥hw|l?RD%Kvu5z(2EJ%RLeVE|hQKAZN?|N?(N-xewo301SniS%i!s_Ng~e9M zi2#qdc@4Z0;uwbs-bQKOHcP8~&bF}4&1FEHGV39{XfHNfXogw-rTqPsLA5PcoO zjHYF^E;w!5!xcMXYO0^%j^o%)R`Yb6HrSCZpksp2CDQ#Hh3EPxpGR6-*7qbo9Ri-O9tUkfB`#S*iLXHblz0g%;ncNhm9$N+t)2H< z$O7I6y+BpbMJf7j&--QSPnoFM^VhF`fSeDpbIun=k%w)d!8tyqj}wiAUP1)N>Yy5<*RkKCv})r8DstxhsNCyB?5^zd%?-POttUea zQ?076TFaikiRpK9r_T@-p1ekx*dn%8rtJ9Zi2q?Id51nz?4|pGv&Vc`4>&}Ycx%Fs zL6K`XVu*D60~a}3RiPFjb-B!blFr`B2atMUj596rci+!JELNx(eg$-(ZIHh!YLcTB zJ7#Cm3Kd4B#jqAa8@v={C|)b#0d!ALtV*DVE_U>4B9g!_VREbk4`K@g zl@MN${8qxOX4}DdX&Q1-M}Q+*bVsu6>-R_f=mT2mBIKuwKwT&OkNU-a=^`)(b%_q> zvj|X*EH3F#Q#CC6dg>irTt?fcmw1Vlqc>4prje5EohsSU`hiJuKQ9&7tGFc0DQ*<# z)$yo7S8-nf+nc)|LJaCm27`uO)w};Nv)U$;>>Kt?m-#RTT&@H9EOU=?5Q&uIC%8!3 zsWu{#z=~90uW}G(^&|llyDDWuMqT9yvK+eFGD>m6KB-bj&6NU$`Xj2-5kB5iq}J(x zK1*TO+(g1&r(yeBb4(+JtHnL)PKlYR1RkFX>{VjII3<3NHFrHUzpKQ1K!TuSt2Saq zW775*%Dhj)Fpi)QVIcDnz35Wm-I}_}u4MlYh4#>|troRf{rW0nP5*K< zC%xWSC*XO%h54`g^H|mjuKYn*hm0hUn01}62I)?5I=k|F5v`gf05|l&W1oVOBNT>J zfh%$vX~+*y{-~$4&@FTYy%QE8z79e1wt&RNX+7Df!5SHbn*itwHgu5;Q+wpG=?58% z2X6^v6SV075O22d7={z{3EH4O_D4x!)+13|=1NmdE~^nxRL$L=^B!=lO(MjheLWf%TSymL z$lnH|g**crWJF4Rp@qCaFD^Yol7F!l(nB7Xqtmj-j_mKz*xmBj$yls^&voRnR>1Ri z+!69vNxg_NHzQ0vh|0A4^&`ME-LFf`;ZkuGuqE+1zz?ymabq=%an0!*00`hsS^_v{ zmcv@8PChRZfS<-nbo0FkJpT-rw)}R($ZS*a?+?A04A(l0ru$L6c1O&s}G0Sv5U~M zC(5ElY|ox2qOJptYVb!8x9+=;yEEp%ot>GGcr+6O7xf&ttu?tMJf8AxKXI~?7QU$q6f(? zFg%e-l5dB^x;V8jN$y@Gn-*Fg_XF?ZnO|SgfKi? z7edW9HO`P2`^OSEM{h+BcapRsiNonKxi_Vpw-2u>quf{CW#yO0*Q&%?ge0N8CT;yE+R)|A#%P$@VnNXs})UZSpy`rS?2WkA%EfhpRa zKvm%gV~RGqR332IwAC^uzJ@=VHoDX=TovrFPHeM5$-BG9_vwOxj``?xy+n8R|i3bJb=TC?Hi# z1gVm=dAs+PDn>x6@MB9ATq>F+dyT>>XZD{!Qe4?zq_a2lAay|wy5OUKfD)RPbW!Mn zkEPeTeIXM6i*>>Ohep5OeNoF{kQd6b2&N!Sj-kPBLohtKo2pUYMAF{1y*BQScjvUf zHEd|RzUZd?twBTEhl+JfaOFmbP8V>fhtPUB%%-Ow>nA-+*kgPZ5Z$WpikU>Rb|P77 z{4S8@6>S)D{s8%M$f_pnLGiWi(mT}`BqK4wiQP$JH;S>dI)^$iwSd}5Lbc5n-&Mh} z|7|f*&%U12xE)A6hpKUFoY+EnD~V(ujYJHvH1&70VQztX+q|rwh3Jugjf>x}2ax~o zs6$bRYDQ$5D~4~frw;l*hlZK z?DA!1BMA$3lY}iontk=${;19rRo`k$phx++%aHI7xtrZt%25gTE2IJObd|RyiOFwJ)%eGk4BQp{;YuZ$lukzIG-MZ z(zxc+g{Th9Uuw>D<}b2$VC6kF2$Ws{Ic#>8zkJDpBVHMfx8a-?s2k<)31e7Qi2jj8 z>nUHyR}_eySu@LLDKOx>W>Fo7yK+5WCGg7bR7} z{woC$FY!ddY8#xQEC~fbIOhO}2ZrOe8reo2;wnzh_g9vaY zx$VadD+<-_BW$S&Ha<>q#c?=w>5gtm5_EXg9*NbNB1uC8Du@=C!irJ%uYd{XJ;4iJ z`LA<8a6BDcThnLd}fL8w;?tt+EOztbe{9jN^2dc+W`j&;DNp4!h)1mqnFA(?wv7vm-v(PZ7 zgmBAC$mPdX%Em~${JnUA7t>hytQ=qrIglqAShp{O|6gqIm2Ll&OtKe2^6t)okY1qq zFYi66rSKT>&AxeifQ2&v8eExo+GpI@1OFtT`w)y^oweaPfKVo+iHDsPjwVk)fUNgR z%mq#ijy0Dc25Vyc>T@BBAvst@(Mkyybr4m=NnoSWYx8#GC?_%-wgUwl*H;A5YQUl# z{@~JUQGYN-s_hGbINh*s+0Y%Ng{JMX^EP(JY=$_)6y|Gh3h-7vc z6a!3dW5>{@`u+k=W29Petl>8JaDNPvPwb42N$16~^~h_gE3d>p4ya7@GK#&Q3l3d| z0o?y$t;DU-Gu=J*!`Z@Z{m%Y(KQP7B|1M{&>3{W9r~j=%0N%aFmw(tfJS;ifqjFHc zIFnfr_=I74-Del4FNhw1HP+7=gXL8uR27KP*9M6dme#uR?o0Q3f>a|oBq(jk!8baO zUf4!K*EdsxvZg!HLTnpEhMxq?5}u%FX|qFym+A1Zx-JubUnl(GO!(mpcQP(y8pzrx zC*WvM0+W3!Z?BIz@Em~)Baz1&07DwN9A1if8NhlLlYqb0O5c9y#<3fG2)LAiB5S)r z@|im|M>FL6e`G6DL^6Dt_96?RBV6LL0`=t*7cQ4WTz-GDBQA!3=Uw>~gyan}HNJ)z zVBFkF5$eumF}VTam%+Iu8z9^pPYd8)VC>$9Td3i5M1C@+AuV+8z$5w67PbGXF*|@=r1Bfi9H^{Y6-(ik_GqM zX(Ng44D?MAk^cC)nqDNO>LQ!Nq1Q0tlMh*hH{qi=QtUqfE=`w1Pm_OFhKW}S0D=oW z(VbFc6{-?wGA1h01Qn5>#By-i*aAsT{w3r<#4{gH%Azciw?-49W++>=H@-M+YJGq) z4!dgBNhn!xH)M^#7$=jS*dVn*4~8XZUC9=5*4%LNFHB*DARY^>*~KIM-U9_ma}DCOP`84z~3d<@Q4+(SIO@q zsF1BmY+7E?U!Zq^;#9w7>OLeXy&jPk*iR^seZu2GL{C@b;5){=dcyKx$4B)?*Rz^q0ovvFPxCC>=d{gi=pwhH1kFh4theSLB=oR;mqb5cYIHW|VDh7-bR zI6IKc5V>C%)0b1M!{rq5E%qg(O89%SGD4=fgBe1VRGJWv| zwbElR@<;`2fhCgPDG+pRu*Dp9R#*BQ|8{C>&eur<_Ju;y?}rAg_61;Xs!>R~*_8<${u)=Y-iB5;QKI{6jla7#Kz=o>1T zMT3~+Op0RcUP=xog6iIY5HU!dN_c0JgOZ(vU{ukyA_juLe zRB38BzG=ShqHYHr+CLO^bSH3bfwNjhi5~}Z;_uZ4SNInKH^hLKw7d_NL4iHoVV(7^nVS>m_dmn^@HB{Qja+(Y=!EOl%*}(!&HKzT$KPF z)5&{8kO~A(l1#+(c*V!vqXJSbuWn33>kFrv<(&*ZUjb^}_1i5z^up(EXGn88$~KW!-n8$G3T;!;sNP>Oy4Vwt9>~0U}*ln{$`oZX&q4-Y0*I=DAq?K+Y-gP)1o$}oq!kh;$6v`w!JKZS;_HbmBhq@76 zMkutP$e0N?RM4WAzl0WU@Tz@t`GUrS_>mVhpr?1ZHz%?Ey}5}Y_xh3jFTnuKw&oiF zbQ}KPjQ@0B=kn9-_A`Dt{(HPRxjz4(!GZ0D49oU6vGHrGl`3S?T|Kw83O__H_A`ob&wQdn;=KtYMEk=;Mf!3;A|M2 z4SQmPi`%;QMpYpi(~rvV98SD^y4!M+v?+fWGQw0c;sI-6Ut_Z*zZhl1w2(bvdcGA& zER~sIZBO?0x7zy+mzIC_eE$VYQQa81^3(ODl&nu(z)aHfiHx2UT@UK0(u{!B)554G z4A#^#bk3Rs86uZKYtQvkd*ySE>4|r3X5AC<4Ay6aE`+zM9KBMuuLNN7>SJ~@Y;M9i z2aBbjzGlqaV2rZIiqg1+<)+U|WFjh%*+@QTmWZh}FkMecrDP~&!UZWFkxl5n#k&)- zNOgiwbfup3g{UNU)p74-i6fmr#_?-$r#*fZkt|WGKK@FkH2oZnX0R@+r;qwENJA~k zi=jZ9Zhf5IVRV4G97(HDfBFi25W`H5!}n@*&hf6<=$!Kq5dzy06HW}(o*a6lkw^FB zdVDQ0&LBoK2M$D^{qU-~!0323rX+bw@sa)fQ_91qJ)08n1%-D+1 zBBVaRjr$N$E#>*EIhE5f>k!i{b(E>ZUD@{9-zWhp%C%inoG6FA(R}{oL=WXpZgetk znL)KxGna@u3;88VIhU&TL6hB&cN+UP zbjcZ7#K5p-;YWfDGRK}{C*-B7H+>~q3N-BHjZf~H$q~%ZRXpLLJm7kBhkpaYC}64v zGN-k3PU0l>5{7E36m3i$4{DpSbNmtdZPdgYR|B>duox8OB#P9hx-c@q+Z$kaUnOAY zUkR|6KKUxy;r|(O;Kl)ouo*Gu3*eNY4Y2r%+Y;xfH6WGTF4S$F_EbM2R}0l?P7cxW zIu4Tr0HOu*16K)~PfcnY0hNKUWWmsXV9CYwmm_<0PX0;wb=bVk&B!GueL0=}AwK!} zGr;C0EY>2^{{%d^9K^o{za0=J;dSdjbiKqUS0OmZsgm=k)70622iM;Mg!;Si?}tQ( zKP^KCp<y+QT_Lu`;5Z)~DQ zHbL;{<+W<{C8RzhU;@x)1Uvw|&7=}-l0UhzLwZo+&7cGe6+PlF00xpF>X=KeRL58| z*>-iDo%LBmd({UzpvkjeM&_}>BF9{b@SlJHwgKtwL(+gqd^KtUJr{>c@L~P&>%Yma z(-!ngul!^gGrdc!UdIXMKDB4h_dPNDqQWfkVq*>t@9Jlq z@$yKP2*pLQeHa(xe|+6Y7o5XP2ObKALyrw;78x1#h=*kj=XqYNQ@BU@4mS7xfZ!Xe z;RmlOLybJMZ|_un(Qni9cO*l8co>7!1>xhIs4!g+hd!+G<<05F^3HMZYYnLvg*;Ih zY@&XX)54*wlTatUo!{bZ$#3Lo$d4*Wixw&} zdb=EYYIc4EG`<3j?_o@z zfUj?f%PeCL{At0m!Ft5A!Acu-m4_?r$Z;2)|3sa?EwQJf0r!yDE#{vMe`yB(*MTwJ zBF_5;_#sE1yqPIkkS3{|Kzhn3B8Cqqwt7_@(iEBh?W9T8H`!EU@1p7aE`g?FvLSeDrG;84Yx0ye)%eO_&{Nh_J*?HoLctK+EccZFG~WsEGT@-2 z&OAT1N`q|3H7kde)PHc+`DacU+U^AA=)m)%XP!G0&(6k%V3@Ldis!qN=NxeEzRVZS z#9S7O);dUR%x9h-J*yYynPpYwDa^8-s8`G#%poiX43nzqZxMaC*F4-mfQbPX5Wk{f@hf)h_?5LE{O-?q^bq-jgI9n?Pb+5^ zTVsJ#DpkLeW3HWJ7!(FZbG`so0P=oyBFW2X@?Qmq1B~o^CEi8jBrCb-Wy><F`gt0Zsq$aK$@)P5z^q!1SNUpLo~%v>$@gPUpdWsyIatrlT<4jo4lY8ACT3 z=HqN@lm#O9w|)gxG&bz;eTtK*CS_%{cpk*?+i5gUxovqfp}L4 zbY0ltUxNTYFq!^`1t!$wzaI|J^%#HRU0;E&2jOHYWSP5hGC#4ybdICDSoq2O#!$+i z0}y9Pf$coF;+>Hu|16?0{TJ{j-t{`L&4v@$!oXHX?(B%@sf-S;-URyz|B*U=B;o^% z$4go)oI9(-5K=fgiJY4JCr(G-Zu;|yVf4mik`<5(Pkizu#CYfLfXELqt3mFMAxgoC z)WjYl_Hpp+uU8ddP$U^KUjxMqcw(e&1rvj|!AN~4+_bL1W{Xk$vN38r`djUu zxO;{HH#s1x)-|d@Z-Y&m=?6lpqkZ({8Pf#XCv^CaI9^Y#U5ISFehM~0N!P=*Q9`4Z%qeudH_Bup95 zbX=xOLTX)Bzq%@N#^&GKV7 z^8>V=WWgaf_Oc7@Resx`1;agA^0t}4QrDNvoOfPV>;lGw;tNOmVzb3rzc3W?|8f`X zM+?-#&|dUmt(s{ThW4fpWzjxQb&C)ZY++V4y~Q|%F+0AJ z6uunb!;oEH>vF+8G9>Oc0UpN?SryX*wCkAmqdHcT4i)IsA5v0WdF<((_=#jeLg}c%`dYEBd5w3&VXu2`H37DqD zyK(yDC!tDgiCfo%uS1l&2mPyI9tqgIN+9A`c@B`a(j8X-ze3;`UJGO9%?!8lM&oBS z$1Ds7kDYw5sE^p8Hlk{{gK(F_*1#ZSgpe3}BN=BGmVaM?q*k!}W5s<6g#eYLCcTU0 zDI44=o?$+t{!0kf1v1PlghlKu-iFvfLy(7Mr+u$C%*H?WEf{0Wd>m|QZ~Zb^VO~>j zT;TImnOC$g9P0Cw#XnC*3(L*N%gh(Li$HCqnOC|F{PLsVWnz48NA8HoEy$fCfV9^@ z&m9eis7Q-Y(-4xu`=*2>gb;J`r3^6t1Vau;NHkV=s3&?7OheR_tfhF{Sai97TTp>Y zd}=MIp-)djzI=-dfe;C_6bV+Vwp< zKBi(;T4~)FCcRV4lS5O?c`)$!%T~3wQ|i8d$&Y-a>D=(h6Ut5~vu#)&MI-c7PJDKX zsVdEXR+^tp2_Er*(fCQU!f1k(esIr-$Fctiu9!3i*Ik~xdK{7*Z4E0bH=hGn{@ww? z$81z<-~K9DwrtX#Mslq&@xSKc)GE>&=DFeODVFcFBi8#Sde-}}MUNdF1DYdLc6!-q z+hnhX?YQ8Id>oc-cp>(QF{a%}c4kNc>|stUn^rbm3RnN$z1x$q{+;lSrmYp45mHh7 z_bD?~h52!n`B}O7wqd@eZb3cLrVevBEHyl(Ze$IZo65nd!m?GI6jhlWmGFhI1l;ss zcsbCl;vwN=LFu)$5xeBj$sBIaD@FsfQy5l5xEVb#QZf-5rk!FiQs545iA08j4eOxt zCxDO2P+f!t&xW?>UTD=vwX*>{YmQhB5kvF6N1}p_XFyzNVOrJrNo+f#5jyQygdoxO z7!S0NnqcE(iWCT#0irl>@`?YLFNBUth-(CjLBg<+r2hH_ir<)wmX)L5fu`}39yXgI zsuAKKBMfZbX$!S76V_85$$~xph>6l>Z?NH6h(q_26e9paJ2ixP)`=cqz{)U57y|vO zd6~%$4@kz$elKJxsTLSNwv;YIFC#Ylh=wrxYXa z-F2MORYHoXCEtfn%E7u|!HB*xSiWJ3xn4^2VzS_vm!w27EXkU>n%OWZHEfuxUJ*q} zv7+dfomzIv>EF&ksqd!xy~J>Q1A92IOztFFwQR@pBFTdDe>vPER+hbne4;O0s`F{S z!lnCHtvZjzVkjCA_2vRv*`1(H;+A^6?NFD}eD=`{rP+b*CR1td)Vacsmt+k$pfxe$I*E}ScC~jQmCVW@qOSDJ9Wu}(P|Bag!hnt$)oELn z0FvPGb&+o(s3Ik;<8OU)?Db=Sy3{`gi-fXnu{RuYOQZkQJ-v9BM!iL2GHO1<1rKj5 zXtWkP2qazzYVvm>ohFW#J^HVrw}<#`RY#@jODdY$2Mb%+pTn7`$-&jA8`?b!TX+2h zCR;MSMkc-VkdqNHK$b_BV}HBgeY{24V>iH#_ak1N0%hGJ`f2n6Uc{y3ko;4BeijWY zU2<&l#XGUd#|munDlZy4WA*%$TYy*^WGtkJR^(fpX(7M+e&o)Nb}%yRGORs5{UfT( znmp%K{7igv-mbLQHFIenJt$f5_%o2z_}Tzs=5}k-;P}?9f~kK}_z!$d@GqwsOE3Iq zd^7y@@6|x3LJd5=2gE`51QCSPO>RE|nj+W;vGc6=KM0RkkDoaj$&-;r-j!__;C%QJ z_=z$ua0VdPggMgZ#UoEYFBTrBIx*N~G^{HmTFpf!NL7Zh1KMGztkts}kiGbvAPY9$ z1zAcKyn6+E?-OIYrJ&gU)!hzMBYcd?d%B&ShzFxH)vP09=Ozn&XD2`1AwZJ`Gnet2 zvLl@cH)VXc36$-ZnP(}{qGuv=ICQv#{`z&~9yG8!W|k|)k@N5CH!}TrnJ`=b8E&RN zsDE#c@ZSF+AM#>#%2gY}qtIi*BXCfi%NC-C$K?`tN;(1-nZ6wamJs9c8Oa7A$yDPH~_CBfG!hc}enLt}tQ6y!(t)4Eeyx@5!gGW#cx8 zIwI|!qh&n9qerok0Qn`#!YVjS@>-9)k_9^;6G>>kis59kAV>Vj@Iqt=b9NJouiP7J zFmVp%lgGWmM&79T{NLiA=LDCY3un1?1cpGc!<+r1(%s9BwT6`i8>b<>%!(w-R-XcH z&Mq&VR$d()JiDT4xXgmvKHV7KJTka+EWyl9!z$a^n9MbcPk`+z+R+?|mE^peethGprIGw8`n5-uY!k3|y zace0GrCVvn7T^w_B~#3o4YTS^Wj#gy1RH7)$ADIzo1I;Xwss^#W|mDt?3IVYT|ddo z6S9@oFhtV4zbv@&xDjJMs0gks8#E>N`7-mXGV_fJsF*#uxHGKIEPpaQansx(=GS93 zPmDepnwy_oTQ>34xo_bUZ1{iB3FReY+b801#+b^|v44Og*!Vm`)l<0i8iS`B5h8Ry zg246uYvJ`Ka9Z3SXM|NS?O~GKnJhT|QXBA>)H7DqV>0}QEi#mhJuR-ngafm2G%XB* zu{QK{;^Dn8-vT^W(~<^X@QAFUBj$g87J^Tz1uX6m}mQqSC7BfE;bY7z~IA z7lHL+m5ql>8NdUGs+E#!a&2(si;^u)^6)pL(?pU5yVz+SreK>@D{%{s&bW@NmC1t7 z)(ywnM6ls99e)RAiNS`8#N(Y}eoUk8WWh@SVgYEu=U#6D!aIQQbRh!NoJd<3NOr}~ z5qb}4fIq|%L`EhHuC%inMl35UPMo+lhDWP^KJmE)gLA9ynfTdUSbYPZlopwuE}6z| z=h z5DCWv!l`7zCu_-q=&Uh|e?IZBxu=@bPbafH=2H`2oS$d5Pn@)V?x1A*#3$!|Xik5? zD#Hf+dLIh=GqQ6uG&yV*23MKuG!tY6!}QvfP}c=S$%1Pnhw@;>hslD?adwdF$rMoi zHhig41M+M@60dNfu1->)pu;+J5#}TdDs;eOI-n+5FaZH&=6cZ+_3PMJQ|lk>F?%=k z{{tb((fgpWCPy!C8_fq*Tl<(Q{Jw?7hmTyG9WuzWS&3dODS-Cx`X$hSy` z%Xfl&50-C5gD--vwY){!Ss`OcT`Lit`K-$nAhM80+MT`J!O`CcmDW%9jT zzE{ZiO8GX+_iFiGBj0P~d!2lLCEpwIrO4mw&1?%>^Do2th8w1*-@R7zf1pVX6mft9 z0~{FOzyJpZI55D00S*jsV1NSy92nrh00#y*Fu;KU4h(Q$fCB>@7~sGF2fkMhr0>s3 zc4f=R6+e2{-@C5=&jvgYHo$=a4h(Q$fCB>@7~sGF2L?DWz<~h{3~*q80|Oiw;K08b z2lO#79?+Z4Q%*Rmb#@3=%!TTuTVPR+gIzryFw6>XHa3fSFif_2S+pg-Tzz1!(Lhw$ zytHGiwBRvEWv#grZ1A{A=vjlGj(hsQghUtQlD z7-VLF$H`^z@BiJ}UwHBx51vm2OM0^+3x2>`%VE%~F8popEV99yjq3oeFPzyeemgy1 zqn6}gLw)oJ50JCzy)Ewi;+UK<@EV#W|lIg5@+XkXv^gz ztC9s@Zkeo97F(e&S#TF_82iTM3#fQ@vFK0!YEl*v+ycF7-A(=qc2g_2Z1a-o-{h~+X@6J-#?DZc3J%#Gye#3_#uMxKY_x___^ss%_Ds^*j;Oz8h z_||QJH?3;Yb4q{d(QQ}l-(PsEDxgBY={Nmm<83%$qxHp(@!RR4z8Hz)OX|w&vZC3t z{8|EQbU_!RLynuUj3 z!-uqm;E7tAC2h9y0KS&+Q~M2%{$_L*U=S9Wj$RrP{5Qj_3aQwyq&7n710Cu3rYgfQ zEAZcg2M6#TdPR7$-{WbEZP!r|g0#tt@ge-;AWaAg&4nYg{2UrMd3ID3G6g#BL$6IH zaURO@9|%X^$EG_0;{R)Rt7Yua^oBo_@UJxV0k8Lb^4|S0-=jXeM6ZAJ zd6{3DtO3^m2L?DWz<~h{3~*q80|Oiw;J^R}1~@RlfdLK-aA1G~|1KP$t;fLMzcmLo zPW0B%8ff58b6|$ekLCfs42Z-42L?DWz<~h{3~*q81OI~@_#AnElrc!lk7#m6+P@+D zcYpiWWB+bC+#`R^uM@6A`7J6c8do&FsJLiC(LqHA7adYmQdBywXxzAQE#bOU9QL7Zr~y9$#EsJfZlY;)9D1DK054 zolrDk+=THHiYH8%aL|N?5#CWpwMU3s;JeQ)?VfBtr{y; z)T(n8HTG6bwJIvr|973UPdF0}VR-r1&z%D+zqR*Sd+)W^Ui)$OnR8CHzq+z|cJ-X< zs_N?Mxz+QkYpUnZ_0O%GJA3Y&xm9zk=gyrwZ*I-p`SbkqD(B6fH)meeyy|&#=gpf} zGjD#4zoxQgcFmlcs+#JWxi#}@YHH@sN5%ORKcB4UlWaawM{6`P=jW?9e!h_7SF@ox zkbEbn8k!5ugKD7phD0TQE0L>2t`fOQxdB&A7C)sXy~2h~9H;iUFbGtpW!lF}q6k&=Hk(0u-ig{1aUGtpW! zlF}q6k&=J&`Da>DEF`s;nu*q;k(4GmiPV`s;>qTX_B)*46e z|0nwn?++DdBmH5p|MdEU`c`4-hnH{pTdKhHruB~gwfbXMf$3Apj7l~)tIcH51eM*d zQ~*4p)A#N$!-sWvV7qCbmvvaaXVpdAV1eZ!{ttcd44-*CTUh&9;>W|`?Q8j0@Y|=B zztz6s0@KHaDK?$3)Um(7^uv-XtNdJn=|gqt4a;NZ_$_%^{vAAUn)NR*eQ0_9gf~?G z0@EM2{spEF)x}fkWakzuA1PmK7<}$D>tA4c%Etvm?U7DX1Iz#V3ruh6ZQ)S+7nnY@ zB9hq{ZLyLotNh@1PP6_6rf(f8*<=Dh8I|ZSF#WLPNBzIR^r6++wB!+5+@?aS)6E87 zuNO1~2B1=?<-4Xm_Umw?4(}u^Heav9ZSX!QsKc=T)Q4AVpE^VJG3~5vZrNLGuWeq6 zF~!~o51RHq1TXeIpu>CM<@_@`EcUK|p4Q=dc#-eZVc7pw2g_;W$epG7hZZM064{Vk z4j4zMZc`){k2J;g%3s<;+Nb;bX8TFoNVrRfVbAKprGjzvoML&QrLlOFMOQ2vOD4$4 z(m(uz(;E*{1*Q+(+?qEeTIJ^oOs}gy1kxAe7%VV-h%fktf(aE`X$*^MD`}*0M+N{3<3>S6hJSApf_ zITM-%AQ@x*KRP{s4iuO^baUQzhOxdZLx`=LdEE@^x?tckA#WcnROF!~5Z-K6mQyPJO;hhws$k z+jO`^hdXt+fv}`c=`d{Z`2T1DX$v`e{z2Nq+PiFOGWFU=2NWG^ero!E5MK5d{W@$) zoxQ(9{lCESto~>9xy?(X5!q~Q(kQR03lJ_nWY)*8!%Bzu%Q@(Lur~UPpS>T9I z9ntweuET>mZ0(;9=eGKLQf&UD(|IbgYeTG-6)&Uz}S0L+xK+`s3|~ zg9WBPUismH)2x4i=|d}GGQ|%a6>zd%%N3Y@D4}MC>R(`bd;5oiM|TE8 zE2Eo3&5=~3IhK{pLL;0q_c82c9wZ>+?bctK_S&bzojSagu+%56!%|t%F|5O||I~-o zAA8PHeJuYB1v3%3+h}5FLq{ZS_|1N-C-yJM+57sDt6#IDDt5 zUuaz7a~l~} z!1{k>+QZs!^y#oEb@u+s+$%@m6w5o&I5a@s(%xmiHtV-Xhm{T!JgbLQKi?^qH{5?} z&{pb`JH`Ag{~0VWeJB`@9K9Q6+yDE`>Ghvff$2kwxVBDq7{8~GhH8T*O8pB=FKO(@ zQ2h%`elDiit!Yo$^{UYN2VOe8_6?pOecx1X zm$b$ArkT$x?7K4mxrMcfjfv#ugfH6J98KxHXVJ4^x|!~$$4S>P%{(8z+Bx5*NQ*C$ zPDi%*64A}Rcr>vg+a~#1M=Y@^5|4?@hK}}V!tlFt+m=)Pf<{(B8`6<>pX`ih8rQ`# z9g(&1l z(?d5|t-`@fHqGvwFPlvI;z@Ix&N#bF>t!bNCDScYb3@veR@gJ6UnHAsk2Md=+6Z=P z`v-J;4R1HGZ?kR0I5YZmnr3lOD6=sZk6*9Urbs##No3*7BOZ;U6SfQ2p2?w5h_Gd|-Qo^xZY zOu^=k3~f9E|Bof2KL5{2r+OXi#JGvp+=G!qCJ(}q8njIOy#wJZZyN8Me~TS zNq*<#OST@jSvEzRvBk0Duw@t1w6yWiozbIZAIs(R@3yToL%-j)A_5j;V$D!^Vp5s6v%}GlQt4!JqdnXd%d{m^M`O0JKqw>AvOk&gd7BO^E6gP9!TXndCV5vmuFzi3|VYNr^8LE$Ef6FMAz0{G@TfdeSnErUb7wjr9 zy`{H>c!zOx7nnY@s3Y$O5>o$*B!^c{v;GC9w|*aQ{ce->SSplZTTst&4OB?RpKhJs zR(SE}tvYPAS66}Mk@W9+cywt!r>LI{+s(F>`T$|6^UlAT?IXNUj}F70)q~;RIC={# zuVa`tdkFiW@ZV0)zfuLJKU4e9rOI4CTl>$4b@^MVi}>e@I@|&;eK1#`eu(}~&TkUhV4qNThrNd{sK7a50toG?UOZ7R?{?%bBTnhO;`S$ne z@Y%M9H68^{vOHgK<)T%$++?Wd+w4=mCZC!?XpGNy?Ti^SMkM=nFjvg#HeY$OPgnG| zlgz-!5rLYZW~c>q)4A!pmRwg_p%Y+7~OJxxxaF5Jl<(b zh@9AW|Fu2&fqQq=(KfL_j!>7Yq z?5u&5Er-9r@|uw4{)KV%6M-L26FsK?9?|(7qyX{f!#XV2KN5afhu7=ypbk&e;TLsy zpUad#q{B5joYP^lDjsLh0@m|%YSh`8pH8G|wk>LOjYu}jjfP|Qm$s3CtPdJWP5suu z%a|R|Vc4^J;GV`2KIQTfP3bosRnfX%Y{g86k^1;YpPK#Y9>x(UFuiRn-ALEQ-wLR= z!1P8U+aIg^z7f*%OuP(p#u3-gb`2j7YsTb{hkhNl(w!}|+BZ;OdF-!68bh0t>5Yw* zjhV(wy18)^J5_SGp*fvx+`y(;M^j@a8#hj|%N2=7{WF`7+59UlH|zTay{~<|`Wg32 zE_^vWk zy2`gXl3}|wo$g3wqb)NnJ=Y8qy~-EMu;DHDPC8_Nax;dI+0l%O4Q(VJ&B4lPWHif( zcSL2LU9vi8s3SYTJSCTkrsM29^1BN*c$?G74EvU#&|zn#s)b@)sB*n_=i|9a(W#!PrF2w96)J+Sd}vMts>Vsi?0_+A3l+K!p2O z7zC@yX$-NfmG;FD(w=f-!Kg|kew}Yivct!wWkQ|jN zW^o&LGsK)mzT7K87fY`#7?yo|Y265hV{>1G-bA}`t3qlbL9_KFha~qU+u6+Lh8Ovq zqCG9cn*BiE-OF?N{C29ttTg7)ZX&XV!JDG3{8ZBy%W4Z-w@zB}GRP}Oce@bHNT&Hc zEiaW)*8SL3Jna-~kNOt3Md`Oo(&=P6PjZ#&PzjY)F;<*WeOxQN<9c$Hd1EKj)W8#psQspyeulEtQ#?NTP8bqzu z)V_qjM$eyE<=giWvR1o!&Zww?ati zGZ=4Vl!?O0-wniO7?aYBCo{;glD|Tt$??jFaAp&=klf`RKf@(gMK|1>iY9m?%#zMq z(T4CX9ntg_es0(iHKPn2iC;`}MG-W@tJTeFDRsS7)lu&ySm#!B-E3G&%2`RM1)are zG3p`XC2zb?t?1u~wWKB;=oQDNSvVOjlbjLD&ZI_-#7B)YDMNnokfofNpix2#IW?22 zp4@Dk)g(FEQfB($ZE59ty!c@KBC!)@iLnldR*Ss7gn-M+;ml%R%Itr~#a6fID+K+ysY7O+pJfvi{cPxdN)i|HVlA0;!)fL-o#uku5~ z56ROi<8K^tJ^)EMPDeNL)?t?qJLGN22}&8AHkskN$oXDKav!9OeaP~gPC_FeC7kZJ z$(3H=RW(qr;1sWFg9c;)5L&&i*13WU|BAkQEII1DAAR`^ zvt7iIm;R8m$xWQ5#MCHkp&v)$%ndBbD~5 zLGt3+h>?mk&Plq`X-DNI+ISI=ysX1sf63qJuE@j?eWD0O3yffa`-VMl zbhg7Is8T#tv_R1U+5#Oj@}KiN+u@?&ixzOQfNN?=`P6BXU00}!Qy0mFI)`g~N%{C` z<6M)~IjK=r+{BXdiPOdtH#T+dNO6-($|p~oh};FKGA%dhdNaRkb$(4#T`Hg#v@R$4 z)uWbNy?DB7={1+6E)HK56#0r(W&+nGCFPe)yO8|PO^vbi(E3iX>1&la%~qzh;GR@x z=255fko*r@`kY@x)mntjM`t#gJiJNd2lp=ov>8X@iqyPQ*g+Gi%)eP(J&_0&1}{c6&3v#(vP`8#70;@}r~ zW5?u@>dA8^QpVA7!m68n>~O4yQ+;*Z6}BTsnljk>1{^eALlP@{7pHtcq;_}83E$0-M z=W|YSPP#bN&8feSpr4aXo%FNIO&X@(eT+eGoF{ud;Upki6zYOudWg(>4Vj9M03Q>% zaXs($s8Q^RyXPubnMai^vaJgo=5%XKakYbgSw1J@;Y4dZbh4RKoGvSGJk)V{V~b9k zH#h12TxlQQ$Cp%(pEHhe$+q@6$u`Kd_x0x5r&8x9b?mh2sPna#Wvv~`ZEJ_&Hp#1l z&ckk3%hq;1&Ujp*W8vw@IiB zW8<9sOvjbm$6M`ZR$EEmnN(q}Ez50d%i-;pHxG_bR-T+weF}|pD#OXPBg{S4I$G*z z&B=MT&}HO}-?|K^^;4b?>vEme+-J;(ooq0I{p@|x>N|OHBjk6YG1#)-3ES6cj&O>b zQc^x;+GX^Ki&7J;KH*fi5p+JGoi8XUxxh2dHR(j-qLW`am0ReX?UYu><@ujeeVyWT z+B`pXvZ+&?lMVD3khey6N~`T+jSEIU()!u`Wm5g9>PzQbzml{{= zRrgGk6cv}iPKMm${C9cO#Y#QraW7DpyT9n>&(Gw~5jD}RZ`}-^moL!Q`cw4vhs^7( zWJbP8pbHu`F1OXAW(MBHpDzCJD}Hsro%TL8>IM4!@Ol13VJG`L#93<+%LhfTzZ!al zJppS?qII{fokV6TC(->{pi<<1zBiJIz;Ll;ArJJicsGt9HC8TMGZIn7b}I@Iz5`!%$6jaqKIMje{J9P+a4XRoKz z_=b*4CNG}osS@6bM$9(nEPG!|W4QgX3pJmZ*cz3L?A zP{yRnlIrv4jB&kAUzB=_uw`=V1{{4I&` z=f41B7fWC4lWyo^p*=IqpVRA$4*L;~Hai{r=zX<0x*v#rzD7mfZnS01LD(Gh8;Hu4 zz0;#A{BF0~J3xfn)fFHDt3Bhs!0lZ!+C3;;@ff=ex1-~Jno~d5W3tm4!@B-3%G3U4 zzpkFNsKmI|zFPZ?)N4n*S1mB=Rq+t)Cm{E@AY&)v&m@mq9(g@hcL$$Be`Ec5t$j_Z z=ks#=e6IIoX4&^-oZ|FaxYoWFcGz39_CQ}{8_)WzJ~wQhkh)lV0d=-Mdf1ZJ*GyOG zb4J}A*6G&R@|A9a~=QdY100A9*+Y9l@Fh_iLYh;j+@+XZdpEPZPYq~P`m5n%uJS;u))}&75JHwC;J=zazxumFpzuw0?84kG6%=7~rIHUb#+o z93jr~6T^NZ$l1%a#)Vovr^vW4W`$S1V6HR9!%l+S9LnVWm67i>`g(V-+n6`Brq$)2aD6vIo9Fd2 zhw;+#L(5;pM~B8-r+Vo+SbHGSj@Gz6%T|Xr%a@((pyQm{#A)ue_jjlJv%|}mG3!gW zc-0%tG2pAPUx(b|eCD;$$2{(o;eW&Xx>I{Q*}$nzI<4K8Exl&g>@w8BK5y zF3$i)k8|t`PWtGw>+F5r!Poem%`v|Ruzq_p`2=&Qb!}w%ecoEi$!<*7}`%->&#P6uCY z4jh)gl{S4lhm9#t<44|Dq3bo-VZS0T&Ou+R?^ zq~Eu$lZL2B0v(WhT-V*k{Q772JC2!Kt@Ktul=QpJ^p63bHq!T+>EG*KcT9RmKXuS^ zq~ERN^`jBe+V`TZy`Fity`DX~Jtr+T_Z!yh{RSDoZ@-l_oVo6JC+vG5_qZJO;^DA< z_pdHjceszKn>9wg-L^;e6-j9LdBrzjUxLPtD|?S@XUd;$`IF<%?f&AFM9 z&9e7hhyG{zg5^t6bt~>>`*rbbvwZ^g{UANI+3!1B^Oe)@0P@y-Bh({rY|(O8ov0oo zjDh9$Jxy%`r!hZo-BIZLqit`$pJwf;Tk9j~pG#kB^zWO2wGiuPb5Et${p(}>NY~l^ z+X8D{KHs)3zmJSQX|8V`fPKWsH%R~B>V|8wd$=ZhPCWG(zZ}7@>}5KQXOZjxE z{n*mmiW9x9y{}1H-aZGt&?uD({{}ELvc`dG`#7M}*~fpYoJuVxWA}pBd)1pw-&_p4 z4B~ziLo3&eOT`gU$x{5um`R0AL`KC>`o9hynx?~YHztE*F z6!f~37rbD!OO3umDe1F+LTT=MzJVtkA+|eA_5E_?;2Pytqh`B$|yQY{!%yco1{@}yEcSvJDd623Ux@x zTUzGk&an8Hh`y?lMGr`)4H1NBSZ98;j-aYmXZ zomS9lL(86m5Iy31WI8V z>#*AR0$rxax;gpyY>)bHs7K2_1b@yPkNSc`S}PBuFEmlU)h7fAj~2A)@h0+#R^e;p zB`AlH$13`Rl~(FJm3Yestg!f$lwpsz!WJ$XcHSNq8%p>@XytGDzQ#!x(QiNC_8V(? zLv!+yb5o^Ej~eZopv;{C9p9u;$`m<4$(#5|SCpvps@|o}bG?HV2H%FqH+`o2s7ce$ zQ5Uw{r7o;`x4Mw?;)!|ZuNXzTS~V)?O?kp@j>9v>_C1I_i_d z<6Pp~O~ZWK>Tg=#`s>Uw;_>Ak^$Zl~HgTxkWE>OO9lDO-r1-fi-trt~{*G(x;Fw%# z|ENARHTsX#qkEibJlFVAHGa()HNI-J8bAB|!SizGhULtZ61OVJma5(ckNPn*vH55n zhQ|%{D;ZPM?$nSES@yOgc6W{OsxkC$#sjQ+Y}kmCKO9DXnmk5Lc3q+_7(73BUjMm$ zqf@2fbNuELMU$>NM_ue%sm{q6HuTH0Hd4Q^+eoh4_}jGAH-_3cY~3rK0 zy-e{aTA*lwq6N;H1*~_iSntP>pJK)t+lL>Yy~>Q+Z!LCh0y0|<5MLJKhn5cKMO5CI zB8R=4|2sXuoHEYK`%8w))iZVuKh9@OBFB2l9Dc|!n>vmz@@^72?0VeAwZ-rwbB!6d z?>PC7RM+8o6~o83fYlBk-e|rv=3GRDDhRKEx}e|D{%@m=uDjNyzDv7&6#x9j)h?B) z;Mv)7mpTA?IX4b^0Qr5;SD@#hUqF9=JaMnO0P;b8s1}l$+|8SlysCst$aB~N9L+XI z-b+ea;=JD5_|9xdzDOIAPu*A%&FLs_g|$L_xi*_lZn1)yXf`A-Sx+Xcm`%}iMuPh5 zzM*(B(xUieE#I(Bhj=f1e9KTs=WLy{vbMtMC@(K=QJbUkdCg2Hli*D&ZAmUTipM{d z1@@yYmu(97H4eQzJncGIeQJ0{!{fjHdf&6H2h8^l4wo$?JbTBFXMFV9LL^UKremAA z&Ngkr=Dc|rL8*rvR-~A3ILSF}R!i)EJqFJ8cG+z*v$k&C&^PJ>d=fbj$g`1?Jl|9a z8Q-7}@eTTr`3?HvNspUOUcWzFm=cdCFQ3kE5YN-`e{;aeZne{!BAHlosFiO#Getpzm#>M0Wb(*n0`;ar9nCsYeoSuBNc+j=nBO`hd|J&*T}hr+f<{6ic)whmAG) zo>B}IEl{*T(E>#a6fID+K+yt43luF-v_R1UMGF)yP_#hN0!0fHEl{*T(E>#a6fID+ zK+yt43luHz&u4+~t6b_G(2<2+)eU|bYPo^$OoBg$0z4tozlwJOuI3$1*Lzg}`~Wn4 zIp356C#>PO;j8&Q6L>o`eI?(g1b+=}gD+d_Qg48c@S=(?@H3E-{J~l4cwXQpzC#E; z2HgqY56-%k=XZITOAveqG(db0cqI>*A3)C_xTw*k9_GDff!Dj#E~t;P`oQ^baH%Z1 zg~4Ay*O9g?lYG``?QHV7Ic{DGS;P-wRfD z5s#id;QQW&t)wns=^eC-=m{Q%o);VPQoSESk7KK{J6&ouG)R03yzbx7gS27r-`?d? zKFaNWH}B$u)}UJsyyb4bNkQ6PaQ=JXNgD>2yqElm?*hLJ9fr>d|31n>Ms>T?UGGOv z_(AaZ|A+P!`495@!VmF#L$NdH`G`xkiT%OPK}W<^;OK4Giu(G%B=kY?0r1CA7rgo? zc7UG7o&oSpPzC*>3;YDsNnQHDm!OBGUGCxi~uO@AwZg!FyfmQK)Yz<$|t{ z;XmkE2G&7GLgc!zTXW#33XzJe(;AM z$L96atH-6@1?|L_dO_C?#u@YsgL|O6MSrm3esre3LGY7MndAe`*hzmthamV#s7mYx zmhZxU&>;YR2r9$QyO%fLumjpAbpd|{RUxk)aH)CFGw2os<9lc$_&zYX7oWjC zJ>bL#X;ZN|xDV=`L%V~wKEzl=eY?P^50gLgLGUo-MK}K=E){{wXwww99SVy-gEu_J z_=635!CyhoEI^-Lm%1Lx!3V*6pe%eZxbYLbqX(PxfMuT~pSk1@_CP7h>I1L+6h4e? zgW%hsGGw~JC!sc8EY}ZS`nXHIOrC!515kf0`GX5SL)*dkfRWGAHyNLMz-vCo*u_gX zyTQ`Wqt86jg56LMz84(#1$+{l_`yTaL3rO2jG54KsUtY~i`Wo80Dc5&U@qtd7k&ww zqFWf;1>MWo(g)W32R;TL1|NVLDv1YQfvVuk`dn%QbO62!+y|w^pTTRM#Q)&~;0`E7 zS$*Kbr|>cSCk%cZ>cby;!NZWEE@fY442J^5hr!Q4*TMILtxr>T@on%WC|HI3e=M)xz`KK2CDP+Z0Y@JVPPI`@OV|3U^I2!LOP4$+Q-;O=J_!{Kw_hriBPM4R@5_kV+N zjI=rM(r*%vef;1K=uWW}IJ+Oer(J^JQ_ui@l>;C67HNskf#*F-Jn}y9MrZOTa4&MuYAM%rS5UhQUJjI6K-=Hqy{og@< zXpnsRz&}9UvuTU(y3{J@Fgm2bZ$aDejY061gV+;31%3kBhR*%qvhU$nylf-|J_cp+ z&pz9DO^U$5hsQ;#~LwAb} z!Kb0;;d5ZsPZ%r2$H1M?LgM?t)K3{VW}y!_`Vixoj3Hpx&uDA>JO^INzOYfPH?+y6y$^flq3WxvM%p~vC7!1oN|!*#^J#5>-8>ry_-4T5ij`e?gu@JT3uZTmsj?-=Xw zhcd7ZYDDKC_%~hU=I}LLA*ZjC8!Gh)t_lkD2q%NcnI1G zul|A$Lu;^q3Ox4+dcp_5uR?9`gMzQnUg*;eF8(WiCH4m|{~Pv!4}$kV+ZYpi!2#%D z(()j)`d4TIdiH>;Ty9kX-vu5P9{c;;Zq*76;xk>~SD-lc&4K$pZnaI?-Ro9AfPC1h zjAtstP?kD&f!~3A)Nv4;#e~zcMB>qN5S)6BTLsa_4>mz5 z=?mZvXbo-M3;qO}PFoLxQ+Xz}8$0;H*Fsr*I|Y6mx);6|{4ul@eh{2K+O2xw1K``C z8g%FZe+s?GTq6&4FNV6Mzk@rV8ph;4aMHPMH5NbggI|Q!Fy`dI;8?fXw}f)Rz0jS= z^nqi~bE`(`>jS?s&aKuX<3Ar+C|6Hf@cld;zFd3*{4L~Tj8Ye1t4rMK8RQkua()In zLRo|0zw(Zk9_FuJ@R!gw%2NO0Rtuo9((d4YLIaGqgW!8Fqt4W^4{Vx@&8cHISbe!$ z-6nMe-v{~7rw9B!^gMZ%U4bnjA9;3z-+>;d+(GafpIbE`9{@K(_hO$e@F0|h9|Xfw z+^ULvy1+k9#s1VKJk71PT#0V*fvemqd9_>lrHD&6Wb_#jw4+pTuO zr@*Ciun*%>7x-V$*0c;)yw?YH(J>Ws82KhnohHAI!lW_z5Ff^UI_k!2W z#mA5df}fpFKf-R^3*722&|&(K?^SNDvD~VRJo`b{LVSQWDg*1GozgB~;Ck{G8-l-p z8fXi3gIm3(mb6k9SROz>_#imHj`~U;1aDl7e`3QvFusJm;B(+h4akeXEu$@Nq(6%< zfekm&?(iva)(Uh$rU!g#rCYs>oy%^<&!KHnU+|YuJvJ;0y45Yv0P!jCzo0U)59q%I zza>5ZegrxI-wR&63L8;(KiCW%q3u%OEvsoq>e~%|8#;^~2Emp!_yqFZ;8W0He4rov zm$i%=j5B`lEzl7e55dXn+^P-v0O);hpeouW2VN4Py{L;Hdd(-I5P@&R*ISJ`7&^ zAU1>#g7-kV*CGRUKZM`H_k)){On-*=gP(<7hR=cLJW9P@M_TZM* zhWVX%-#4hocj!ChqrOL4C`)`0{5*6JJ_mm2`}hESKltxIpkCzD4}R@=`hw{HL+TAZ zBW(xX3+hls+%<;If}F-bq^U z?w_+xgYO4N{Q@7tCO&X2R3>%phaUtx26@blw7!?z>QB(il;!^| z>j9{<5k0}=?#x!Xou=e+iX~cJfPeOgf_k-jAK)(|ia04^|-vvJPXXX#& zbAMreeuZ(E_|#vKDdGJZ#QV?js0W~Jk`MU$Qjfa(^`r$q0d>Onfm3rC@Vn6C@M^3_ZHIc``@uuwJnCim)cL$W=t7Tbk+fiZf=68j zp93=&lNP@05|3H}^}~n3yP$*cJzy`CgI5zh>g~|PH;@l_@g$F`f%k*mkWb>lZ$cID zIk4(dkMhF@!B0Q~u7eMNUxTXP)fFDq1U*68F7Pqvh~y9c5PBJY5cK&xY69|pa0zr7 zd=UH$^dd6-pqj$-&BT|1S3+Y&9(*(O3^Lu|ho*Ye^YA%vBky_YmU!?#pr<7t@cii> zbpYN6ehlh@?*q@}{YG0Q9$XD|!iT~4L-mx^16uDu68sKyka)o#K!fmuVB?h@wFkZn zj9x|E;rqb9L1V+{U*S=6ATN9XybYQF-wjq>jcwt>;OJ`z!w10)&}Hzwpt=s5iww9G zx(>bv{64fCeh~cA40MJM%=D;VKx^QAvpgyRg+(6x64WO0;B|idQslv}LWd+CoXLBU zvhZEt#k|+3M`XZFyw~Umd_Q7pob)RCA-o@43+;msgZ~Eg!FPlA2Rv#& zyjtW@<Z*zks|=#MgV&ze8i;`@vC5==<C1?3_8PbZz5%`)9JL%D7J2Yn z(BtqqaMud_Q+Uw75*xw?!4BwQ`0krMYGn}p$fpZ@8ES<0-GV)#ID86xCzO@4z}?Vo z@O@y(Ds+bTgK6k)kpZ8D?u8!&gKN;C1>L~wZ>8T6p95c9?@@;(?Q3{{*X!wP62Hl# zeggHvtIgC4+6OORGOB`9J^lm!J5&ZQujxMoRj^K$*S24>mGS6i^atyqgSSv$Fbh3_ zyu2oV7xX;x^0xf%LNCJSz-jM7|61|~@&z`M#`a5*=GRPKnuxJKBx9h;RDzWd>q=ph_b*#(1beb z1zvJD?ILR`uwICHP+o_bg`Ob33w!{&jX7OjkNaJThtGjt-v7FdIbB{0dlNK)HHo|( z_MOmlczH?ee?e8UZUO)0z2pxsuZ+DFS|jq{JE0bl2OpPs<|BD;;6bR2@mAi?H~D>> zgO^v_H9}n>{0F=X+Q!%-?`)Izv>kvKd=`2hUf#D>(M{XYr{o=8Vdx1erMtv zH9o9yP~+^E&GVfapU|j&Z^qYa+^X?w8pr*?Jikig2Q>af<28RY&!;u+(E9Z2aM@w= z{3eY#jhFt(j1OvjLgODaR{dG0)A)IfSN_F}4{O}1@db^gN6hmL8rNxkF4g#>m(24| zXuMbBZ5kUj1~mS5(3Ib;ah1lu{>F@dR^xpdZ`YX8*rIX&uTA+yI_%T&y<2~AL zXU04Je!0Z8y*Fv>()ezTJ2XC_aX{m5HI8}3w2x2Y4I1B|@pg^(Xnb5_lTQEltEpMi zvuC;7F@H1dF+tW|FxKN{8>pNSA|K5IevgPd`FxySiEx;!${(FK7{?F04XRvSYPba9r zi$~*rL|o4y$QJi`srS!_`^8bYtunXej)v{J73XKWHIC00=V#M@iu3dFedOzsL+i~K zh=!u+bTVze;qWze>w`-c)l1rsh(xl37b&-f(vidl{nA96#8oA=9f{^h2k$`+4ZX=U zV`bDpDeox>t;lxrvgPKD>is6)kuXAUCHFOL>E!0n;#6vw2BP{8UA3WCN3$E^$)Fg;pZ#_SVJ{0p8AOiUjKG*M?R!WwN|eIvWU>br9i(x+Qh?Gh&@e;snv4evZM{ z85Vn$w-$Sy*-U4NjWcbr)>F&3PAh9D_&L+2&5;x3wYn~J6P2Tm>slkt7(`osc0i)4 zW(UMS=DKQY=jFdpbI7`1_tb7kCR+l>IjxeO+SYhvL&iZs+(s=esoflF$+kJ158a5s zlH;APs|(#2x)JqeNw2V8A6nq+Q#4_SDxoseqp5?z;v^eJykfmEnm$E)FeIH)24YRWU;0gix}2st)UTu`Cm7sLMHk($ zgGL^)#OdVmf{{mLQzRBYc}*CJr7thTJ5H}%Rxy_Z6Q@^`n~gk9uO_cxjb#aD;ZyN2 zEOF#?(i`J7J_a`U{gvWrH5YZ7)w`5J`6;soX;XNpHET0 zrLklxny}Ug6tOlGOUjF_WooH!F#6eB?c*;~$jGmLwnpBs8@_ ziMcK;sm;csCzI3|BiC8BJ<&UwjU|eDpSL#MBK5VB6`*2%IVYY^%#vcvv9b8kepygK z#)v=GK6jolnKf&c+f$v$7{(gTSf}CwLnb_N)hYub8Jb-i%L*J}cb_G)fR^OHN__|K0-RVaiyN3Wqm%}Gf$^_Y0;TQn{eTHM)bTt6-8 zY>t{=y5TZ0B+*OLk@jdqB+(L&rmJ}DMapK~qWHoN=R=uPv^mzwM+(|Z5gvjfzPTfv zjwWo^aq2RsSTsvyqn%m4)#H>G7hdLtpepCQ)M@CA^C7K!D2^{EHIwt3n$uai$Xk(Y z>4-;{^38;mXnree_u=Qv+T5xttuxE{e9!V&Q#z8~vZV7?wQljMwJX;wTd`!QRqQdL zO0%WBSS_8cZ`!abno6d#s+!y)q60HfsFh1uRqqNhU!u5tZXv`l-OAU2Qpp%~PG{6> zy{*mhWG2d|O1-T{FIMV}-d0jHtBnSxk{NZow^bJH>JD!!xwNQvd0Vl*obkCru+fdN zxmD^eZ|mlCj1L&8%S$-DiL%t|N;%{n=c=9uqlHT}x2M#1J&~r2dd?e9Cp!`?O1PNHkypi>1Z_6(zHR9xI^uRlN+J!D#TTJoDY$-se@kg zgi=qrLz!rsD2mBK(my_fTr$xb+t87Yh7x?eglqmtdc!8A-sIw21hH%^5|6!k=u|YZ zDV9zq+Hppu(s>feM6@%O4P_%uaqRYKcPJg*fHP5!J|k^teDvvOip~^m?#M>1Q)Z&4 z(1rHqBA<-ZlU6A7rjA(iMv@Szb`ooHCHekxt5R80ABt>O9Y^_4l(m*b#uD;Ps_Vq| z^!reTuL~J`1fSwXD95y0#%woE-s=f5%x#ROBH1>P-A6c+ipR1#qNQYWb4KUcK^<7n zMO%#V=e-_TbKab2jxNF+2?>}MRqq=*vpCtFibuuUmb&S1i_Ol?_<@5i!zcEYtd3^a zCd?6`WoSsDOHwQ4@{neAHj>VEq?YmVh+EldA;#>sR@YR$o@JSOzIw7@Lo^*sM>EoZ z+q|nHu}pL*L#3v9S9HX)u|-?5(KX3iV=d9ed>%zDA7!y?l0)a!+q}1QMAKV>(R6Dv z%^c7iU5pEhMvH8<2&R+G(M*PFTx#gK1&?T76KjvwWn@SZPAziP$1>tJW|j5Gu^9Kw zsCx`6nI>Dk9Diz%Ui%&?VL6|`St6TbO5NrmI#nk_yy`OerF09W4vCyrK_ryAY}C3K zmWsp;<&`?%5=mX8`k^IX*V2NKXz81ltXjEb`JCBCV~)JlS3Fy^K+ytc$O5i^#AC#| z6{4`tz$VI<*#3@(V+GEB`3}YY?hcEO8Fh)NAn0y1Lsne7paWhk@7PrhO`BK^M(Id zSlihi_vugZUO%;Rrhlq0n&1{}EV1GGscY9Pol!H@#}00yB@*Y7?)s@)qM50+H=H+S zVI-4@wl~GM_{boUxqfO#I`-kv)=A1B-Ia6<-TqGCB_km;FHsu4tR@CNLq9wUG!(~$zqx&A+|LDO-2Od5AXz61U zA5;B2SIYU$p3?0N+qZ7tzrA$FL?ml=l-}QV|DO8??yuO{wsX(Uft?k*+IH>PHL$B< zciZkgy9ahxJka^T)(5sdu;+o!Jp+3V?RxbMM=hdLkH_t4>oY98)< zcm%D9DSfoz(XEtT`dGtb>mS?yn97-Dm-aOD?CIIpv%lwH&p^*%G?}=4 z&yI%s*WbVY{)sy)cGm1{*g0`m=dOLbCho4-U9-Dk_e9Fx_rT!?YW8&Q*+EgM0T-cFjW#4-FV)?|b<0!wrwDe`Nn76_3_Dx`#T~Jl6TxzQ+zfrUrSQoO;*v zZ0*@biR-s-+rDpmAN3#Het5guQHB+KJ5u*|-rt1<{OAxsi{Q@nJHyx`wX<_)*Uqgw zyLWEe*|T&1&VKAOxU*t!&EAH+>#@PU2lqdC@WFux_dPW6;q}!2;KLKqV$UP{(4@_1 z0aFhYP+rx@--@0ZtgybP0u2ssA3%rw+5$DZ8mM{cZmaI=vH$)D+Nk-!o`$_!sdedt z4G*rT#F~c!*uCqatq*lSwC$muhj>dI{Z delta 45346 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 WP*OXqHd>olr`&msH^=prUH=DEfxblm diff --git a/JoshHeaps.Net/Services/Implementations/ChessEngineFactory.cs b/JoshHeaps.Net/Services/Implementations/ChessEngineFactory.cs index 34b7c55..2ccd590 100644 --- a/JoshHeaps.Net/Services/Implementations/ChessEngineFactory.cs +++ b/JoshHeaps.Net/Services/Implementations/ChessEngineFactory.cs @@ -7,7 +7,10 @@ namespace JoshHeaps.Net.Services.Implementations; public enum ChessEngineKind { Stockfish, - Custom + Custom, + + ///

The custom engine with the reinforcement-learned piece-square evaluation. + CustomLearned } /// Configuration selecting which to use. @@ -19,14 +22,19 @@ public sealed class ChessEngineOptions } /// Creates the configured per game. -public sealed class ChessEngineFactory(IOptions options) : IChessEngineFactory +public sealed class ChessEngineFactory( + IOptions options, + ILearnedWeightsStore weightsStore) : IChessEngineFactory { private readonly ChessEngineKind _kind = options.Value.Engine; - public IChessEngine Create(int skill) => _kind switch + public IChessEngine Create(int skill) => Create(skill, _kind); + + public IChessEngine Create(int skill, ChessEngineKind kind) => kind switch { ChessEngineKind.Custom => new CustomChessEngine(skill), + ChessEngineKind.CustomLearned => new CustomChessEngine(skill, EngineVariant.Learned, weightsStore.WeightsFilePath), ChessEngineKind.Stockfish => new Stockfish(skill), - _ => throw new InvalidOperationException($"Unknown chess engine '{_kind}'.") + _ => throw new InvalidOperationException($"Unknown chess engine '{kind}'.") }; } diff --git a/JoshHeaps.Net/Services/Implementations/ComputerMoveOrchestrator.cs b/JoshHeaps.Net/Services/Implementations/ComputerMoveOrchestrator.cs index 244b6f6..540979c 100644 --- a/JoshHeaps.Net/Services/Implementations/ComputerMoveOrchestrator.cs +++ b/JoshHeaps.Net/Services/Implementations/ComputerMoveOrchestrator.cs @@ -11,23 +11,60 @@ namespace JoshHeaps.Net.Services.Implementations; ///
public sealed class ComputerMoveOrchestrator( IHubContext chessHub, - IChessService chessService) : IComputerMoveOrchestrator + IChessService chessService, + ILearnedWeightsStore weightsStore) : IComputerMoveOrchestrator { - public async Task<(MoveDto move, MoveResultDto result)> PlayAsync(GameState state, IChessEngine engine) + public async Task<(MoveDto move, MoveResultDto result)> PlayAsync(GameState state) { + var engine = (state.CurrentPlayer == PieceColor.White ? state.WhiteComputer : state.BlackComputer) + ?? throw new InvalidOperationException($"No engine is set for {state.CurrentPlayer} in game {state.GameId}."); + var uci = await engine.GetBestMoveAsync(state.ToFen(), state.RepetitionHistory()); + var move = uci.ToMoveDto(state, CurrentPlayerId(state)); - var move = uci.ToMoveDto( - state, - state.CurrentPlayer == PieceColor.White - ? state.WhitePlayerId - : state.BlackPlayerId); + return await ApplyAndBroadcastAsync(state, move); + } + public async Task PlayRandomMoveAsync(GameState state) + { + var options = chessService.GetAllLegalMoves(state); + + // No legal moves means the game is already over; let the caller's loop detect it. + if (options.Count == 0) + return; + + var (piece, moves) = options[Random.Shared.Next(options.Count)]; + var target = moves[Random.Shared.Next(moves.Count)]; + + var move = new MoveDto + { + GameId = state.GameId, + PlayerId = CurrentPlayerId(state), + PieceId = piece.Id, + SourceRow = piece.Position.Row, + SourceCol = piece.Position.Col, + TargetRow = target.Row, + TargetCol = target.Col, + PromotionChoice = null // a pawn cannot reach the last rank within the opening plies + }; + + await ApplyAndBroadcastAsync(state, move); + } + + private async Task<(MoveDto move, MoveResultDto result)> ApplyAndBroadcastAsync(GameState state, MoveDto move) + { var result = chessService.MakeMove(state, move); + // Record the played position for training (no-op for non-training games). + if (state.Trainer != nint.Zero) + weightsStore.Record(state.Trainer, state.ToFen()); + await chessHub.Clients.Group(state.GameId.ToString()) .SendAsync("ReceiveMoveUpdate", state.GameId.ToString(), move, result, state.ToDto()); return (move, result); } + + private static Guid CurrentPlayerId(GameState state) => + state.CurrentPlayer == PieceColor.White ? state.WhitePlayerId : state.BlackPlayerId; } diff --git a/JoshHeaps.Net/Services/Implementations/CustomChessEngine.cs b/JoshHeaps.Net/Services/Implementations/CustomChessEngine.cs index 5675a69..d7bb8fd 100644 --- a/JoshHeaps.Net/Services/Implementations/CustomChessEngine.cs +++ b/JoshHeaps.Net/Services/Implementations/CustomChessEngine.cs @@ -5,6 +5,16 @@ using System.Runtime.InteropServices; namespace JoshHeaps.Net.Services.Implementations; +/// Which evaluation the native engine uses. +public enum EngineVariant +{ + /// The hand-crafted evaluation. + Classic, + + /// Material plus a learned per-square bonus table loaded from a weights file. + Learned +} + /// /// Middleman wrapper over the native custom chess engine (chess_engine.dll / libchess_engine.so). /// Shares with so the two are swappable. @@ -16,11 +26,17 @@ public sealed partial class CustomChessEngine : IChessEngine public int Skill { get; } - public CustomChessEngine(int skill = 20) + public CustomChessEngine(int skill = 20, EngineVariant variant = EngineVariant.Classic, string? weightsPath = null) { Skill = skill; - var handle = NativeMethods.engine_create($"skill={skill}"); + // weights= must come last: the native side reads the path as the rest of the + // string, which lets it contain ';' and spaces. + var options = variant == EngineVariant.Learned + ? $"skill={skill};variant=learned;weights={weightsPath}" + : $"skill={skill}"; + + var handle = NativeMethods.engine_create(options); if (handle == IntPtr.Zero) throw new InvalidOperationException("Native chess engine failed to initialize (engine_create returned null)."); @@ -79,8 +95,9 @@ public sealed partial class CustomChessEngine : IChessEngine /// /// P/Invoke surface for chess_engine.(dll|so). The resolver maps the logical name /// "chess_engine" to the platform binary in the Resources folder (mirrors Stockfish). + /// Internal so can share the single import resolver. /// - private static partial class NativeMethods + internal static partial class NativeMethods { private const string LibName = "chess_engine"; @@ -115,5 +132,31 @@ public sealed partial class CustomChessEngine : IChessEngine [LibraryImport(LibName)] [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] internal static partial void engine_destroy(IntPtr engine); + + // ---- Learned-weights / training ABI (see chess_engine.h) ---- + + [LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void learned_load(string path); + + [LibraryImport(LibName)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static unsafe partial int weights_snapshot(int* outBuf, int outLen); + + [LibraryImport(LibName)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial IntPtr trainer_create(); + + [LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void trainer_record(IntPtr trainer, string fen); + + [LibraryImport(LibName)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void trainer_apply(IntPtr trainer, int winner, double weight); + + [LibraryImport(LibName)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + internal static partial void trainer_destroy(IntPtr trainer); } } diff --git a/JoshHeaps.Net/Services/Implementations/LearnedWeightsStore.cs b/JoshHeaps.Net/Services/Implementations/LearnedWeightsStore.cs new file mode 100644 index 0000000..992f387 --- /dev/null +++ b/JoshHeaps.Net/Services/Implementations/LearnedWeightsStore.cs @@ -0,0 +1,64 @@ +using JoshHeaps.Net.Models; +using JoshHeaps.Net.Services.Interfaces; + +namespace JoshHeaps.Net.Services.Implementations; + +/// +/// Managed facade over the native learned-weights model (see ). +/// On construction it points the native engine at the weights file; everything else delegates +/// to the shared native ABI in . +/// +public sealed class LearnedWeightsStore : ILearnedWeightsStore +{ + private const int Pieces = 6; // Pawn..King + private const int Squares = 64; + private const int Features = 8; // mobility N/B/R/Q, passed, isolated, doubled, king safety + + public string WeightsFilePath { get; } + + public LearnedWeightsStore(IHostEnvironment env) + { + WeightsFilePath = Path.Combine(env.ContentRootPath, "chess-data", "learned-weights.txt"); + Directory.CreateDirectory(Path.GetDirectoryName(WeightsFilePath)!); + CustomChessEngine.NativeMethods.learned_load(WeightsFilePath); + } + + public nint CreateTrainer() => CustomChessEngine.NativeMethods.trainer_create(); + + public void Record(nint trainer, string fen) => + CustomChessEngine.NativeMethods.trainer_record(trainer, fen); + + public void ApplyResult(nint trainer, PieceColor winner, double weight) => + CustomChessEngine.NativeMethods.trainer_apply(trainer, winner == PieceColor.White ? 0 : 1, weight); + + public void DestroyTrainer(nint trainer) => + CustomChessEngine.NativeMethods.trainer_destroy(trainer); + + public LearnedWeightsSnapshot Snapshot() + { + const int total = Pieces * Squares * 2 + Features; + var buffer = new int[total]; + + unsafe + { + fixed (int* p = buffer) + CustomChessEngine.NativeMethods.weights_snapshot(p, total); + } + + var mg = new int[Pieces][]; + var eg = new int[Pieces][]; + + for (int piece = 0; piece < Pieces; piece++) + { + mg[piece] = new int[Squares]; + eg[piece] = new int[Squares]; + Array.Copy(buffer, piece * Squares, mg[piece], 0, Squares); + Array.Copy(buffer, Pieces * Squares + piece * Squares, eg[piece], 0, Squares); + } + + var features = new int[Features]; + Array.Copy(buffer, Pieces * Squares * 2, features, 0, Features); + + return new LearnedWeightsSnapshot(mg, eg, features); + } +} diff --git a/JoshHeaps.Net/Services/Interfaces/IChessEngineFactory.cs b/JoshHeaps.Net/Services/Interfaces/IChessEngineFactory.cs index 3f87e24..02b4990 100644 --- a/JoshHeaps.Net/Services/Interfaces/IChessEngineFactory.cs +++ b/JoshHeaps.Net/Services/Interfaces/IChessEngineFactory.cs @@ -1,3 +1,5 @@ +using JoshHeaps.Net.Services.Implementations; + namespace JoshHeaps.Net.Services.Interfaces; /// @@ -6,9 +8,20 @@ namespace JoshHeaps.Net.Services.Interfaces; public interface IChessEngineFactory { /// - /// Creates a new engine instance for a single game. The caller owns and disposes it. + /// Creates a new engine instance for a single game using the configured default engine. + /// The caller owns and disposes it. /// /// The desired playing strength / search depth. /// A new, owned . IChessEngine Create(int skill); + + /// + /// Creates a new engine instance for a single game using an explicitly chosen engine + /// (e.g. for picking a different engine per side in a CPU-vs-CPU game). The caller owns + /// and disposes it. + /// + /// The desired playing strength / search depth. + /// The engine implementation to create. + /// A new, owned . + IChessEngine Create(int skill, ChessEngineKind kind); } diff --git a/JoshHeaps.Net/Services/Interfaces/IComputerMoveOrchestrator.cs b/JoshHeaps.Net/Services/Interfaces/IComputerMoveOrchestrator.cs index 6fcd34d..a4aa7ff 100644 --- a/JoshHeaps.Net/Services/Interfaces/IComputerMoveOrchestrator.cs +++ b/JoshHeaps.Net/Services/Interfaces/IComputerMoveOrchestrator.cs @@ -3,16 +3,24 @@ using JoshHeaps.Net.Models; namespace JoshHeaps.Net.Services.Interfaces; /// -/// Drives a computer move: asks the engine for a move, applies it through the rules -/// service, and broadcasts the result to the game's clients. +/// Drives a computer move: asks the side-to-move's engine for a move, applies it through +/// the rules service, and broadcasts the result to the game's clients. /// public interface IComputerMoveOrchestrator { /// - /// Has the engine pick a move for the current position, applies it, and broadcasts it. + /// Has the side-to-move's engine pick a move for the current position, applies it, and + /// broadcasts it. The engine is taken from the game's per-side computer assignments. /// /// The game to play a move in. - /// The engine that selects the move. /// The applied move and its result. - Task<(MoveDto move, MoveResultDto result)> PlayAsync(GameState state, IChessEngine engine); + Task<(MoveDto move, MoveResultDto result)> PlayAsync(GameState state); + + /// + /// Plays a uniformly-random legal move for the side to move (no engine), applying and + /// broadcasting it. Used to randomize the opening of training games so self-play and + /// engine-vs-engine games don't replay the same line every time. + /// + /// The game to play a random move in. + Task PlayRandomMoveAsync(GameState state); } diff --git a/JoshHeaps.Net/Services/Interfaces/ILearnedWeightsStore.cs b/JoshHeaps.Net/Services/Interfaces/ILearnedWeightsStore.cs new file mode 100644 index 0000000..b0d3491 --- /dev/null +++ b/JoshHeaps.Net/Services/Interfaces/ILearnedWeightsStore.cs @@ -0,0 +1,33 @@ +using JoshHeaps.Net.Models; + +namespace JoshHeaps.Net.Services.Interfaces; + +/// +/// Thin managed facade over the native learned-weights model. The weights, all feature +/// computation, per-game accumulation, the update rule, and persistence live in the native +/// engine; this just points it at the weights file, hands out per-game trainers, and reads +/// the table back for visualization. +/// +public interface ILearnedWeightsStore +{ + /// Absolute path to the weights file the native engine loads and saves. + string WeightsFilePath { get; } + + /// A copy of the current weights (midgame/endgame tables + feature weights). + LearnedWeightsSnapshot Snapshot(); + + /// Creates a per-game training accumulator. The caller owns it (see ). + nint CreateTrainer(); + + /// Records one played position (post-move FEN) into a trainer. + void Record(nint trainer, string fen); + + /// + /// Applies a finished game's outcome to the global weights (and saves): rewards the + /// winner's squares/features, punishes the loser's, scaled by . + /// + void ApplyResult(nint trainer, PieceColor winner, double weight); + + /// Frees a trainer. Safe to call with . + void DestroyTrainer(nint trainer); +} diff --git a/JoshHeaps.Net/wwwroot/css/chess/spectate.css b/JoshHeaps.Net/wwwroot/css/chess/spectate.css index d47db37..297fe93 100644 --- a/JoshHeaps.Net/wwwroot/css/chess/spectate.css +++ b/JoshHeaps.Net/wwwroot/css/chess/spectate.css @@ -21,11 +21,27 @@ html, body { display: flex; align-items: center; justify-content: center; - gap: 0.5rem; + flex-wrap: wrap; + gap: 0.75rem; margin: 1rem 0; } -#cpuDifficulty { +.enginePicker { + display: flex; + align-items: center; + gap: 0.4rem; + border: 1px solid #444; + border-radius: 8px; + padding: 0.3rem 0.6rem 0.5rem; +} + +.enginePicker legend { + color: #9a9a9a; + font-size: 0.8rem; + padding: 0 0.3rem; +} + +#watchControls select { background-color: #1e1e1e; color: #d6d6d6; border: 1px solid #444; diff --git a/JoshHeaps.Net/wwwroot/css/chess/weights.css b/JoshHeaps.Net/wwwroot/css/chess/weights.css new file mode 100644 index 0000000..91e2a90 --- /dev/null +++ b/JoshHeaps.Net/wwwroot/css/chess/weights.css @@ -0,0 +1,187 @@ +body { + margin: 0; + background-color: #141414; + color: #d6d6d6; + font-family: "Segoe UI", system-ui, sans-serif; +} + +#weightsHeader { + text-align: center; + padding: 1.5rem 1rem 0.5rem; +} + +#weightsHeader h1 { + margin: 0 0 0.25rem; + font-size: 1.6rem; +} + +#weightsStatus { + color: #9a9a9a; + margin: 0.25rem 0 1rem; +} + +#weightsControls { + display: flex; + align-items: center; + justify-content: center; + gap: 1.25rem; + margin-bottom: 0.5rem; +} + +.heatLegend { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.8rem; + color: #9a9a9a; +} + +.legendBar { + width: 120px; + height: 10px; + border-radius: 5px; + background: linear-gradient(to right, rgba(232, 74, 74, 1), #2a2a2a, rgba(74, 134, 232, 1)); +} + +#refreshWeights { + background-color: #8cd5ed; + color: #262626; + border: 0; + border-radius: 30px; + padding: 0.5rem 1.1rem; + cursor: pointer; +} + +#refreshWeights:hover { + background-color: #a5e0f2; +} + +#backToWatch { + display: inline-block; + margin-top: 0.5rem; + color: #8cd5ed; + text-decoration: none; +} + +.weightsSection { + margin: 0 auto; + max-width: 1200px; + padding: 0.5rem 1rem; +} + +.weightsSection h2 { + text-align: center; + font-size: 1.2rem; + margin: 1rem 0 0.25rem; +} + +.sectionHint { + text-align: center; + color: #9a9a9a; + font-size: 0.8rem; + margin: 0 0 0.75rem; +} + +.weightsGrid { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 1.25rem; + padding: 0.5rem 0; +} + +.featurePanel { + max-width: 520px; + margin: 0 auto; + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.featureRow { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.featureLabel { + flex: 0 0 6.5rem; + font-size: 0.8rem; + color: #c8c8c8; + text-align: right; +} + +.featureTrack { + flex: 1 1 auto; + height: 14px; + background-color: #232323; + border-radius: 7px; + overflow: hidden; +} + +.featureBar { + height: 100%; + border-radius: 7px; + min-width: 2px; +} + +.featureValue { + flex: 0 0 3rem; + font-size: 0.8rem; + color: #e8e8e8; + text-align: left; +} + +.weightBoard { + background-color: #1c1c1c; + border: 1px solid #2e2e2e; + border-radius: 10px; + padding: 0.75rem; +} + +.weightBoardHeader { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; + font-weight: 600; +} + +.weightBoardIcon { + width: 22px; + height: 22px; +} + +.weightRange { + margin-left: auto; + font-weight: 400; + font-size: 0.75rem; + color: #888; +} + +.miniHeat { + display: grid; + grid-template-columns: 1.1rem repeat(8, 34px); +} + +.heatSquare { + width: 34px; + height: 34px; + box-sizing: border-box; + border: 1px solid #2a2a2a; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.62rem; + color: #f0f0f0; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); +} + +.heatLabel { + display: flex; + align-items: center; + justify-content: center; + padding: 2px 0; + font-size: 0.6rem; + color: #777; +} diff --git a/JoshHeaps.Net/wwwroot/js/ChessScripts/Spectate.js b/JoshHeaps.Net/wwwroot/js/ChessScripts/Spectate.js index 08f3450..d576798 100644 --- a/JoshHeaps.Net/wwwroot/js/ChessScripts/Spectate.js +++ b/JoshHeaps.Net/wwwroot/js/ChessScripts/Spectate.js @@ -27,10 +27,15 @@ const Spectate = { }, async startCpuGame() { - const difficulty = document.getElementById("cpuDifficulty").value; + const params = new URLSearchParams({ + whiteEngine: document.getElementById("whiteEngine").value, + whiteSkill: document.getElementById("whiteSkill").value, + blackEngine: document.getElementById("blackEngine").value, + blackSkill: document.getElementById("blackSkill").value + }); try { - await fetch(`/api/chess/watch/cpu/${difficulty}`); + await fetch(`/api/chess/watch/cpu?${params}`); await this.refreshGames(); } catch (err) { console.error("❌ Could not start CPU vs CPU game.", err); @@ -68,7 +73,9 @@ const Spectate = { async addGame(game) { this.games.set(game.gameId, { isVsComputer: game.isVsComputer, - isComputerVsComputer: game.isComputerVsComputer + isComputerVsComputer: game.isComputerVsComputer, + whiteEngine: game.whiteEngine, + blackEngine: game.blackEngine }); const card = document.createElement("div"); @@ -183,11 +190,21 @@ const Spectate = { }, gameLabel(stored) { - if (stored.isComputerVsComputer) return "CPU vs CPU"; + if (stored.isComputerVsComputer) + return `${this.engineName(stored.whiteEngine)} (W) vs ${this.engineName(stored.blackEngine)} (B)`; if (stored.isVsComputer) return "Vs CPU"; return "Player vs Player"; }, + engineName(kind) { + switch (kind) { + case "CustomLearned": return "Learned"; + case "Custom": return "Custom"; + case "Stockfish": return "Stockfish"; + default: return kind || "CPU"; + } + }, + headerText(stored, currentPlayer, moveCount, isCheck) { if (stored.result) return `${this.gameLabel(stored)} · move ${moveCount} · final`; diff --git a/JoshHeaps.Net/wwwroot/js/ChessScripts/Weights.js b/JoshHeaps.Net/wwwroot/js/ChessScripts/Weights.js new file mode 100644 index 0000000..4dff00b --- /dev/null +++ b/JoshHeaps.Net/wwwroot/js/ChessScripts/Weights.js @@ -0,0 +1,158 @@ +const Weights = { + async init() { + await this.refresh(); + }, + + async refresh() { + let data; + + try { + const response = await fetch("/api/chess/weights"); + data = await response.json(); + } catch (err) { + console.error("❌ Could not load weights.", err); + document.getElementById("weightsStatus").textContent = "Could not load weights."; + return; + } + + this.renderBoardSet(data.mg, "weightsGridMg"); + this.renderBoardSet(data.eg, "weightsGridEg"); + this.renderFeatures(data.features); + + const trained = [...data.mg, ...data.eg].some(b => b.squares.some(v => v !== 0)) + || data.features.some(f => f.value !== 0); + + document.getElementById("weightsStatus").textContent = trained + ? "Where the learned engine thinks each piece belongs. Blue = preferred, red = avoided. Midgame vs endgame tables are blended by how much material is left." + : "No training yet — everything is neutral. Run some learned games on the Watch page."; + }, + + // pieces: [{ name, squares[64] }]. Prepends an "Overall" board summing the set. + renderBoardSet(pieces, containerId) { + const overall = new Array(64).fill(0); + for (const piece of pieces) + for (let sq = 0; sq < 64; sq++) + overall[sq] += piece.squares[sq]; + + const boards = [{ name: "Overall", squares: overall }, ...pieces]; + const grid = document.getElementById(containerId); + grid.innerHTML = ""; + boards.forEach(board => grid.appendChild(this.buildBoard(board))); + }, + + buildBoard(board) { + const wrapper = document.createElement("div"); + wrapper.className = "weightBoard"; + + const maxAbs = board.squares.reduce((m, v) => Math.max(m, Math.abs(v)), 0); + + const header = document.createElement("div"); + header.className = "weightBoardHeader"; + if (board.name !== "Overall") { + const icon = document.createElement("img"); + icon.src = `/images/Chess Images/White${board.name}.svg`; + icon.alt = board.name; + icon.className = "weightBoardIcon"; + header.appendChild(icon); + } + const title = document.createElement("span"); + title.textContent = board.name; + header.appendChild(title); + const range = document.createElement("span"); + range.className = "weightRange"; + range.textContent = maxAbs === 0 ? "neutral" : `±${maxAbs}`; + header.appendChild(range); + wrapper.appendChild(header); + + const heat = document.createElement("div"); + heat.className = "miniHeat"; + + for (let row = 0; row < 8; row++) { + const rankLabel = document.createElement("div"); + rankLabel.className = "heatLabel"; + rankLabel.textContent = 8 - row; // rank 8 at top, 1 at bottom + heat.appendChild(rankLabel); + + for (let col = 0; col < 8; col++) { + const rank = 7 - row; // rank index, 0 = rank 1 + const sq = rank * 8 + col; // white-relative square (A1 = 0) + heat.appendChild(this.buildSquare(board.squares[sq], sq, maxAbs)); + } + } + + heat.appendChild(this.cornerSpacer()); + for (let col = 0; col < 8; col++) { + const fileLabel = document.createElement("div"); + fileLabel.className = "heatLabel"; + fileLabel.textContent = String.fromCharCode(97 + col); + heat.appendChild(fileLabel); + } + + wrapper.appendChild(heat); + return wrapper; + }, + + buildSquare(value, sq, maxAbs) { + const cell = document.createElement("div"); + cell.className = "heatSquare"; + + if (value !== 0 && maxAbs > 0) { + const ratio = Math.abs(value) / maxAbs; + const alpha = (0.12 + 0.88 * ratio).toFixed(3); + cell.style.backgroundColor = value > 0 + ? `rgba(74, 134, 232, ${alpha})` // high -> blue + : `rgba(232, 74, 74, ${alpha})`; // low -> red + cell.textContent = value; + } + + const file = String.fromCharCode(97 + (sq & 7)); + const rank = (sq >> 3) + 1; + cell.title = `${file}${rank}: ${value}`; + return cell; + }, + + cornerSpacer() { + const spacer = document.createElement("div"); + spacer.className = "heatLabel"; + return spacer; + }, + + // features: [{ name, value }] + renderFeatures(features) { + const panel = document.getElementById("featureWeights"); + panel.innerHTML = ""; + + const maxAbs = features.reduce((m, f) => Math.max(m, Math.abs(f.value)), 0); + + features.forEach(feature => { + const row = document.createElement("div"); + row.className = "featureRow"; + + const label = document.createElement("span"); + label.className = "featureLabel"; + label.textContent = feature.name; + + const track = document.createElement("div"); + track.className = "featureTrack"; + const bar = document.createElement("div"); + bar.className = "featureBar"; + const ratio = maxAbs === 0 ? 0 : Math.abs(feature.value) / maxAbs; + bar.style.width = `${(ratio * 100).toFixed(1)}%`; + bar.style.backgroundColor = feature.value >= 0 + ? "rgba(74, 134, 232, 0.85)" + : "rgba(232, 74, 74, 0.85)"; + track.appendChild(bar); + + const value = document.createElement("span"); + value.className = "featureValue"; + value.textContent = feature.value; + + row.append(label, track, value); + panel.appendChild(row); + }); + } +}; + +window.addEventListener("load", () => Weights.init()); + +console.log("Weights.js loaded"); diff --git a/native/chess_engine/include/chess_engine.h b/native/chess_engine/include/chess_engine.h index 9caca72..e8af337 100644 --- a/native/chess_engine/include/chess_engine.h +++ b/native/chess_engine/include/chess_engine.h @@ -32,6 +32,9 @@ extern "C" { /* prevent C++ name mangling */ * Internally it points to your engine state object. */ typedef struct ChessEngine* EngineHandle; +/* Opaque per-game training accumulator (see the learned-weights ABI at the bottom). */ +typedef struct Trainer* TrainerHandle; + /* Return codes. 0 == success; negative == error. Keep these values stable. */ enum { CHESS_OK = 0, @@ -79,6 +82,32 @@ CHESS_API int CHESS_CALL engine_version(char* out_buf, int out_len); /* Destroy an instance created by engine_create. Safe to call with NULL. */ CHESS_API void CHESS_CALL engine_destroy(EngineHandle engine); +/* ---- Learned-weights / training ABI ---- + * The learned engine's weights live process-globally here. The host orchestrates games but + * owns no chess logic: it points the engine at the weights file, records each played + * position, and applies the game's result. */ + +/* Load the global learned weights from `path` and remember it for later saves. Idempotent; + * a missing/short file leaves the weights neutral. Call once before learned play/training. */ +CHESS_API void CHESS_CALL learned_load(const char* path); + +/* Copy the global weights out for visualization: 6*64 midgame + 6*64 endgame (PAWN..KING, + * squares 0..63) + feature weights. Returns the count written, or CHESS_ERR_BUFFER if + * out_len is too small (needs >= 776). */ +CHESS_API int CHESS_CALL weights_snapshot(int* out, int out_len); + +/* Create / destroy a per-game training accumulator. Safe to destroy NULL. */ +CHESS_API TrainerHandle CHESS_CALL trainer_create(void); +CHESS_API void CHESS_CALL trainer_destroy(TrainerHandle trainer); + +/* Record one played position (post-move FEN) into the accumulator. */ +CHESS_API void CHESS_CALL trainer_record(TrainerHandle trainer, const char* fen); + +/* Apply a finished game's outcome to the global weights and save: rewards the winner's + * occupied squares / features and punishes the loser's, scaled by `weight` (e.g. 0.5 for a + * material-imbalance draw). winner: 0 = white, 1 = black. */ +CHESS_API void CHESS_CALL trainer_apply(TrainerHandle trainer, int winner, double weight); + #ifdef __cplusplus } #endif diff --git a/native/chess_engine/src/chess_engine.cpp b/native/chess_engine/src/chess_engine.cpp index a674594..c32a252 100644 --- a/native/chess_engine/src/chess_engine.cpp +++ b/native/chess_engine/src/chess_engine.cpp @@ -20,10 +20,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -76,10 +78,63 @@ static chess::Move tt_move (uint64_t d) { return chess::Move(static_cast(static_cast(d >> 48)); } static Bound tt_bound(uint64_t d) { return static_cast(static_cast(d >> 56)); } -/* Internal engine state. One ChessEngine = one game. The table is NOT here: it is the - * shared g_tt above. */ +/* Eval variant for an engine handle. CLASSIC = the hand-crafted evaluate(); LEARNED = + * material + learned phase-split piece-square tables + learned feature weights. */ +enum EvalVariant : int { EVAL_CLASSIC = 0, EVAL_LEARNED = 1 }; + +/* The learned feature knobs (beyond the piece-square tables). Each has one weight learned + * from game outcomes; its activation is computed by compute_features(). Mobility is per + * piece type. Order is fixed — it is the on-disk and snapshot layout after the two tables. */ +enum Feature : int { + FEAT_MOB_N, FEAT_MOB_B, FEAT_MOB_R, FEAT_MOB_Q, /* legal-move counts, per piece type */ + FEAT_PASSED, /* passed pawns, endgame-weighted */ + FEAT_ISOLATED, /* isolated pawns */ + FEAT_DOUBLED, /* doubled pawns */ + FEAT_KING, /* king pawn-shelter, midgame-weighted */ + FEATURE_NB +}; + +/* Per-handle eval configuration, snapshotted from the global learned weights at + * engine_create so the search reads a stable copy. The tables are white-relative: a black + * piece indexes the rank-mirrored square (sq ^ 56). `mg`/`eg` are blended by game phase. + * Indexed by chess::PieceType (PAWN..KING). Only consulted when variant == EVAL_LEARNED. */ +struct EvalParams { + int variant = EVAL_CLASSIC; + int mg[chess::PIECE_TYPE_NB][64] = {}; + int eg[chess::PIECE_TYPE_NB][64] = {}; + int featW[FEATURE_NB] = {}; +}; + +/* Internal engine state. One ChessEngine = one game. The transposition table is NOT + * here: it is the shared g_tt above. */ struct ChessEngine { - int skill = 20; /* 1..20 from the UI; controls search depth */ + int skill = 20; /* 1..20 from the UI; controls search depth */ + EvalParams eval; /* which evaluation the search uses, plus any learned weights */ +}; + +/* The process-global learned weights: the single source of truth, loaded from disk once and + * updated in place by training. Engine handles snapshot it at creation; the visualization + * snapshots it on demand. Guarded by g_weightsMutex for updates/saves (eval reads its own + * per-handle copy, so it never touches this concurrently). */ +struct LearnedWeights { + int mg[chess::PIECE_TYPE_NB][64] = {}; + int eg[chess::PIECE_TYPE_NB][64] = {}; + int featW[FEATURE_NB] = {}; +}; + +static LearnedWeights g_weights; +static std::mutex g_weightsMutex; +static std::string g_weightsPath; + +/* Per-game training accumulator (one per learned CPU-vs-CPU game). Records, per ply, where + * each side's pieces sat (split into midgame/endgame by phase) and each side's feature + * activations; trainer_apply turns the totals into weight nudges. Squares are white-relative + * (black indexes sq ^ 56), so a side's tally lines up with the shared white-relative table. */ +struct Trainer { + double mgOcc[chess::COLOR_NB][chess::PIECE_TYPE_NB][64] = {}; + double egOcc[chess::COLOR_NB][chess::PIECE_TYPE_NB][64] = {}; + double featAcc[chess::COLOR_NB][FEATURE_NB] = {}; + int plies = 0; }; static int copy_out(const char* src, char* out_buf, int out_len) { @@ -108,6 +163,48 @@ static int parse_skill(const char* options, int fallback) { return v < 1 ? 1 : v > 20 ? 20 : v; } +/* "variant=learned" in the options selects the learned eval; anything else is classic. */ +static int parse_variant(const char* options) { + if (!options) return EVAL_CLASSIC; + const char* p = std::strstr(options, "variant="); + if (!p) return EVAL_CLASSIC; + return std::strncmp(p + 8, "learned", 7) == 0 ? EVAL_LEARNED : EVAL_CLASSIC; +} + +/* On-disk format: 6*64 mg ints (PAWN..KING, squares 0..63), then 6*64 eg ints, then + * FEATURE_NB feature ints, whitespace-separated. A missing file or short read leaves the + * rest neutral (0), so an absent weights file just means "train from a blank slate". + * Caller holds g_weightsMutex. */ +static void load_global_weights(const char* path) { + g_weights = LearnedWeights{}; /* reset to neutral before loading */ + + if (!path || !*path) return; + std::ifstream f(path); + if (!f) return; + + for (int pt = chess::PAWN; pt <= chess::KING; ++pt) + for (int sq = 0; sq < 64; ++sq) + if (!(f >> g_weights.mg[pt][sq])) return; + for (int pt = chess::PAWN; pt <= chess::KING; ++pt) + for (int sq = 0; sq < 64; ++sq) + if (!(f >> g_weights.eg[pt][sq])) return; + for (int i = 0; i < FEATURE_NB; ++i) + if (!(f >> g_weights.featW[i])) return; +} + +/* Persist g_weights to g_weightsPath in the format load_global_weights reads. Caller holds the lock. */ +static void save_global_weights() { + if (g_weightsPath.empty()) return; + std::ofstream f(g_weightsPath); + if (!f) return; + + for (int pt = chess::PAWN; pt <= chess::KING; ++pt) + for (int sq = 0; sq < 64; ++sq) f << g_weights.mg[pt][sq] << (sq == 63 ? '\n' : ' '); + for (int pt = chess::PAWN; pt <= chess::KING; ++pt) + for (int sq = 0; sq < 64; ++sq) f << g_weights.eg[pt][sq] << (sq == 63 ? '\n' : ' '); + for (int i = 0; i < FEATURE_NB; ++i) f << g_weights.featW[i] << (i == FEATURE_NB - 1 ? '\n' : ' '); +} + /* Maps the 1..20 difficulty to a search depth. Kept modest: the search has no * quiescence yet, so deep fixed-depth runs get expensive quickly. */ static int depth_for_skill(int skill) { @@ -279,10 +376,136 @@ static int evaluate(const chess::Position& pos) { return score; } +/* ---- Learned (phase-split tables + feature knobs) evaluation --------------------------- + * The model is a linear combination of features whose weights are learned from outcomes: + * eval = Σ pieces [ material + blend(mg, eg, phase) ] + Σ features featW[i]·activation[i] + * compute_features() is the single source of feature activations, used by BOTH the eval here + * and the trainer, so the two can never disagree. Constants below are the only tunables. */ + +/* Per-game-outcome learning rates and clamps. Squares accumulate occupancy (plies on a + * square, summed); features accumulate normalized per-ply activation (averaged, divided by a + * nominal scale so high-magnitude mobility doesn't dwarf the small pawn-structure terms). */ +static constexpr double SQUARE_LR = 0.5; +static constexpr int SQ_CLAMP = 250; +static constexpr double FEAT_LR = 2.0; +static constexpr int FEAT_CLAMP = 500; +static constexpr double FEAT_SCALE[FEATURE_NB] = { 4, 6, 8, 14, 2, 1, 1, 2 }; + +/* Game phase in [0,1] from remaining non-pawn material (PeSTO weights N=B=1, R=2, Q=4; max + * 24 for both full sides): 0 = opening, 1 = bare kings. Drives the mg/eg table blend and + * the phase weighting of the passed-pawn (×phase) and king-safety (×(1−phase)) features. */ +static double game_phase(const chess::Position& pos) { + int npm = chess::popcount(pos.pieces(chess::KNIGHT)) * 1 + + chess::popcount(pos.pieces(chess::BISHOP)) * 1 + + chess::popcount(pos.pieces(chess::ROOK)) * 2 + + chess::popcount(pos.pieces(chess::QUEEN)) * 4; + constexpr int MAX = 24; + if (npm >= MAX) return 0.0; + return double(MAX - npm) / MAX; +} + +/* Blend a midgame and endgame value by phase, rounding per-piece (so training credits a + * square the same way the eval reads it). */ +static int blend(int mg, int eg, double phase) { + return int(std::lround((1.0 - phase) * mg + phase * eg)); +} + +/* Fills `out[FEATURE_NB]` with one color's raw feature activations for a position. The piece- + * square tables handle "where pieces belong"; these capture context a static table can't: + * legal mobility (per piece type, so pins reduce it), passed pawns (endgame-weighted), pawn + * structure, and king shelter (midgame-weighted). Ported nowhere — this is the only copy. */ +static void compute_features(chess::Position& pos, chess::Color c, double phase, double out[FEATURE_NB]) { + for (int i = 0; i < FEATURE_NB; ++i) out[i] = 0.0; + + /* Mobility: legal moves for color c, bucketed by the moving piece's type. */ + chess::MoveList moves; + pos.generate_legal_for(c, moves); + for (int i = 0; i < moves.size(); ++i) { + switch (chess::type_of(pos.piece_on(moves.moves[i].from()))) { + case chess::KNIGHT: out[FEAT_MOB_N] += 1; break; + case chess::BISHOP: out[FEAT_MOB_B] += 1; break; + case chess::ROOK: out[FEAT_MOB_R] += 1; break; + case chess::QUEEN: out[FEAT_MOB_Q] += 1; break; + default: break; + } + } + + /* Pawn structure. */ + chess::Bitboard pawns = pos.pieces(c, chess::PAWN); + chess::Bitboard bb = pawns; + while (bb) { + chess::Square s = chess::pop_lsb(bb); + + if (!(front_span(c, s) & pos.pieces(~c, chess::PAWN))) { /* passed */ + chess::Rank r = chess::rank_of(s); + int toPromotion = (c == chess::WHITE) ? (chess::RANK_8 - r) : (r - chess::RANK_1); + out[FEAT_PASSED] += (6 - toPromotion) * phase; /* 0..5 ranks advanced, late-game */ + } + if (front_span_file_only(c, s) & pawns) /* doubled (friendly pawn ahead) */ + out[FEAT_DOUBLED] += 1; + + chess::File f = chess::file_of(s); + chess::Bitboard adjacent = 0; + if (f > chess::FILE_A) adjacent |= chess::file_bb(chess::File(f - 1)); + if (f < chess::FILE_H) adjacent |= chess::file_bb(chess::File(f + 1)); + if (!(adjacent & pawns)) /* isolated */ + out[FEAT_ISOLATED] += 1; + } + + /* King safety: friendly pawns sheltering the king (its file + adjacent files, the two + * ranks in front), worth more in the midgame. */ + chess::Square k = pos.king_square(c); + chess::File kf = chess::file_of(k); + chess::Rank kr = chess::rank_of(k); + chess::Bitboard kingFiles = chess::file_bb(kf); + if (kf > chess::FILE_A) kingFiles |= chess::file_bb(chess::File(kf - 1)); + if (kf < chess::FILE_H) kingFiles |= chess::file_bb(chess::File(kf + 1)); + chess::Bitboard shelterRanks = 0; + for (int d = 1; d <= 2; ++d) { + int rr = (c == chess::WHITE) ? (kr + d) : (kr - d); + if (rr >= 0 && rr <= 7) shelterRanks |= (0xFFULL << (8 * rr)); + } + out[FEAT_KING] += chess::popcount(kingFiles & shelterRanks & pawns) * (1.0 - phase); +} + +/* Learned eval (white-positive/absolute, like evaluate()): material + phase-blended piece- + * square tables + learned feature weights. Black pieces index the rank-mirrored square + * (s ^ 56) so both colors share one white-relative table. Non-const because mobility + * generates legal moves (which the position's move generator does via do/undo). */ +static int evaluateLearned(chess::Position& pos, const EvalParams& ep) { + double phase = game_phase(pos); + int score = 0; + + chess::Bitboard white = pos.pieces(chess::WHITE); + while (white) { + chess::Square s = chess::pop_lsb(white); + chess::PieceType pt = chess::type_of(pos.piece_on(s)); + score += piece_value(pt) + blend(ep.mg[pt][s], ep.eg[pt][s], phase); + } + + chess::Bitboard black = pos.pieces(chess::BLACK); + while (black) { + chess::Square s = chess::pop_lsb(black); + chess::PieceType pt = chess::type_of(pos.piece_on(s)); + score -= piece_value(pt) + blend(ep.mg[pt][s ^ 56], ep.eg[pt][s ^ 56], phase); + } + + double wFeat[FEATURE_NB], bFeat[FEATURE_NB]; + compute_features(pos, chess::WHITE, phase, wFeat); + compute_features(pos, chess::BLACK, phase, bFeat); + + double feature = 0.0; + for (int i = 0; i < FEATURE_NB; ++i) + feature += ep.featW[i] * (wFeat[i] - bFeat[i]) / FEAT_SCALE[i]; + score += int(std::lround(feature)); + + return score; +} + /* evaluate() is white-positive (absolute). Negamax needs it relative to the side to * move, so flip the sign when black is to move. */ -static int evaluate_stm(const chess::Position& pos, bool whiteToMove) { - int s = evaluate(pos); +static int evaluate_stm(chess::Position& pos, bool whiteToMove, const EvalParams& ep) { + int s = (ep.variant == EVAL_LEARNED) ? evaluateLearned(pos, ep) : evaluate(pos); return whiteToMove ? s : -s; } @@ -358,6 +581,15 @@ CHESS_API EngineHandle CHESS_CALL engine_create(const char* options) { auto* e = new (std::nothrow) ChessEngine(); if (!e) return nullptr; e->skill = parse_skill(options, e->skill); + e->eval.variant = parse_variant(options); + if (e->eval.variant == EVAL_LEARNED) { + /* Snapshot the current global weights so the search reads a stable copy (training + * updates the global between games; the weights path is owned by learned_load). */ + std::lock_guard lock(g_weightsMutex); + std::memcpy(e->eval.mg, g_weights.mg, sizeof e->eval.mg); + std::memcpy(e->eval.eg, g_weights.eg, sizeof e->eval.eg); + std::memcpy(e->eval.featW, g_weights.featW, sizeof e->eval.featW); + } return e; } @@ -376,8 +608,9 @@ CHESS_API int CHESS_CALL engine_set_option(EngineHandle engine, static constexpr int MAX_PLY = 128; /* ply never exceeds maxDepth (<= 20) */ struct SearchContext { - uint64_t nodes = 0; - chess::Move killers[MAX_PLY][2] = {}; /* [ply][slot]; MOVE_NONE until filled */ + uint64_t nodes = 0; + const EvalParams* eval = nullptr; /* eval config for this search; set by engine_best_move */ + chess::Move killers[MAX_PLY][2] = {};/* [ply][slot]; MOVE_NONE until filled */ }; /* Negamax alpha-beta over the shared transposition table. `maxDepth` is the searching @@ -394,7 +627,7 @@ static int negamax(chess::Position& pos, int maxDepth, int depth, int ply, return 0; if (depth <= 0) - return evaluate_stm(pos, whiteToMove); + return evaluate_stm(pos, whiteToMove, *ctx.eval); const uint64_t key = pos.key(); TTEntry& slot = g_tt.entries[key & g_tt.mask]; @@ -509,6 +742,7 @@ CHESS_API int CHESS_CALL engine_best_move(EngineHandle engine, return CHESS_ERR_NO_MOVE; SearchContext ctx; + ctx.eval = &engine->eval; int maxDepth = depth_for_skill(engine->skill); chess::Move bestMove = moves.moves[0]; /* guaranteed-legal fallback */ @@ -554,4 +788,100 @@ CHESS_API void CHESS_CALL engine_destroy(EngineHandle engine) { delete engine; /* delete nullptr is safe */ } +/* ---- Learned-weights / training C ABI -------------------------------------------------- + * The managed side orchestrates games but owns no chess logic: it tells the engine where + * to load/save the global weights, records each played position, and applies the result. */ + +CHESS_API void CHESS_CALL learned_load(const char* path) { + std::lock_guard lock(g_weightsMutex); + g_weightsPath = path ? path : ""; + load_global_weights(path); +} + +CHESS_API int CHESS_CALL weights_snapshot(int* out, int out_len) { + const int need = 6 * 64 * 2 + FEATURE_NB; /* mg + eg (PAWN..KING) + features = 776 */ + if (!out || out_len < need) return CHESS_ERR_BUFFER; + + std::lock_guard lock(g_weightsMutex); + int n = 0; + for (int pt = chess::PAWN; pt <= chess::KING; ++pt) + for (int sq = 0; sq < 64; ++sq) out[n++] = g_weights.mg[pt][sq]; + for (int pt = chess::PAWN; pt <= chess::KING; ++pt) + for (int sq = 0; sq < 64; ++sq) out[n++] = g_weights.eg[pt][sq]; + for (int i = 0; i < FEATURE_NB; ++i) out[n++] = g_weights.featW[i]; + return n; +} + +CHESS_API TrainerHandle CHESS_CALL trainer_create(void) { + return new (std::nothrow) Trainer(); +} + +CHESS_API void CHESS_CALL trainer_record(TrainerHandle t, const char* fen) { + if (!t || !fen || !*fen) return; + ensure_initialized(); + + chess::Position pos = chess::Position::from_fen(fen); + double phase = game_phase(pos); + + /* Per-square occupancy, split into midgame/endgame by phase, white-relative. */ + chess::Bitboard occ = pos.pieces(); + while (occ) { + chess::Square s = chess::pop_lsb(occ); + chess::Piece pc = pos.piece_on(s); + chess::Color c = chess::color_of(pc); + chess::PieceType pt = chess::type_of(pc); + int relSq = (c == chess::WHITE) ? int(s) : (int(s) ^ 56); + t->mgOcc[c][pt][relSq] += (1.0 - phase); + t->egOcc[c][pt][relSq] += phase; + } + + /* Per-side feature activations. */ + double w[FEATURE_NB], b[FEATURE_NB]; + compute_features(pos, chess::WHITE, phase, w); + compute_features(pos, chess::BLACK, phase, b); + for (int i = 0; i < FEATURE_NB; ++i) { + t->featAcc[chess::WHITE][i] += w[i]; + t->featAcc[chess::BLACK][i] += b[i]; + } + + t->plies++; +} + +CHESS_API void CHESS_CALL trainer_apply(TrainerHandle t, int winner, double weight) { + if (!t) return; + + std::lock_guard lock(g_weightsMutex); + + /* pass 0 = winner (reward, +1); pass 1 = loser (punish, -1). */ + for (int pass = 0; pass < 2; ++pass) { + chess::Color side = chess::Color((pass == 0 ? winner : (winner ^ 1)) & 1); + int sign = pass == 0 ? 1 : -1; + + for (int pt = chess::PAWN; pt <= chess::KING; ++pt) + for (int sq = 0; sq < 64; ++sq) { + if (t->mgOcc[side][pt][sq] != 0.0) { + int d = sign * int(std::lround(SQUARE_LR * t->mgOcc[side][pt][sq] * weight)); + g_weights.mg[pt][sq] = std::clamp(g_weights.mg[pt][sq] + d, -SQ_CLAMP, SQ_CLAMP); + } + if (t->egOcc[side][pt][sq] != 0.0) { + int d = sign * int(std::lround(SQUARE_LR * t->egOcc[side][pt][sq] * weight)); + g_weights.eg[pt][sq] = std::clamp(g_weights.eg[pt][sq] + d, -SQ_CLAMP, SQ_CLAMP); + } + } + + if (t->plies > 0) + for (int i = 0; i < FEATURE_NB; ++i) { + double avg = t->featAcc[side][i] / t->plies; /* per-ply average, normalized */ + int d = sign * int(std::lround(FEAT_LR * (avg / FEAT_SCALE[i]) * weight)); + g_weights.featW[i] = std::clamp(g_weights.featW[i] + d, -FEAT_CLAMP, FEAT_CLAMP); + } + } + + save_global_weights(); +} + +CHESS_API void CHESS_CALL trainer_destroy(TrainerHandle t) { + delete t; /* delete nullptr is safe */ +} + } /* extern "C" */ diff --git a/native/chess_engine/src/position.h b/native/chess_engine/src/position.h index 8976394..f615ffb 100644 --- a/native/chess_engine/src/position.h +++ b/native/chess_engine/src/position.h @@ -47,6 +47,22 @@ public: void do_move(Move m); void undo_move(Move m); + // Legal moves for a SPECIFIED color (for mobility eval of either side). When c is not + // the side to move, temporarily flips side-to-move (and clears the en-passant square, + // which belongs to the other side) so generate_legal runs for c, then restores. The + // Zobrist key is untouched and unused by move generation, so this leaves the position + // observably unchanged. + void generate_legal_for(Color c, MoveList& list) { + if (sideToMove == c) { generate_legal(list); return; } + Color savedSide = sideToMove; + Square savedEp = epSquare; + sideToMove = c; + epSquare = SQ_NONE; + generate_legal(list); + sideToMove = savedSide; + epSquare = savedEp; + } + // Seed prior-position keys (oldest first, excluding the current position) so // is_draw() can see game history the FEN doesn't carry. Call once, right after // from_fen and before any do_move.