From 18a5f352a1762a1b4f0fe372adba72baceecf9d5 Mon Sep 17 00:00:00 2001 From: Paul Hutchinson Date: Sun, 14 Dec 2025 15:08:56 +0000 Subject: [PATCH 1/2] An attempt at some optimisations --- components/Book/Page.tsx | 17 +-- components/Book/SixGamePage.tsx | 62 ++++---- components/Book/index.tsx | 97 +++++++++++++ next.config.js | 2 + public/pieces/background.png | Bin 0 -> 55501 bytes styles/globals.css | 6 +- types.ts | 14 +- utils/convertGamesToPages.ts | 247 +++++++++++++++++--------------- 8 files changed, 285 insertions(+), 160 deletions(-) create mode 100644 public/pieces/background.png diff --git a/components/Book/Page.tsx b/components/Book/Page.tsx index 5ad2f55..77fe064 100644 --- a/components/Book/Page.tsx +++ b/components/Book/Page.tsx @@ -19,11 +19,9 @@ export default function Page( ) { return (
{props.pageNumber !== 0 && (
{props.pageNumber}
diff --git a/components/Book/SixGamePage.tsx b/components/Book/SixGamePage.tsx index bbce0bd..e3ef331 100644 --- a/components/Book/SixGamePage.tsx +++ b/components/Book/SixGamePage.tsx @@ -6,14 +6,14 @@ import ContentPage from "./ContentPage"; import MoveListTable from "../Chess/MoveListTable"; import MoveListInline from "../Chess/MoveListInline"; -function GameRow(props: { index: number; settings: Settings; game: Game, totalMoveRows: number, fullLength: Boolean }) { +function GameRow(props: { settings: Settings; game: Game, otherGameMoves: number }) { const moves = props.game.moves.split(" "); // Find the best variant to use to get the most out the move data. let size = "text-xs"; return ( -
+
- {props.totalMoveRows > 35 * (Number(props.fullLength) + 1) && moves.length > 40 * (Number(props.fullLength) + 1) + {(props.otherGameMoves + props.game.movesCount) < 65 * 2 ? ( - + ) : ( - + )}
); @@ -38,32 +38,42 @@ export default function SixGamePage(props: PageProps & { games: Game[] }) { return b.movesCount - a.movesCount; }); - const totalMoveRows = sortedByMoveCountGames[0].movesCount + (sortedByMoveCountGames[3]?.movesCount ?? 0) / 2; + const columns: [number, number | undefined][] = []; + const offset = sortedByMoveCountGames.length - 1; + + for (let i = 0; i < 3; i++) { + if (!sortedByMoveCountGames[i]) { + break; + } + + const newColumn: [number, number | undefined] = [i, undefined]; + const key = offset - i; + if (offset - 1 > 2 && sortedByMoveCountGames[key]) { + newColumn[1] = key; + } - let yGap = 'gap-y-4'; - if (totalMoveRows < 80) { - yGap = 'gap-y-20'; - } else if (totalMoveRows < 160) { - yGap = 'gap-y-12'; - } else if (totalMoveRows < 220) { - yGap = 'gap-y-8'; - } else { - yGap = 'gap-y-4'; + columns.push(newColumn); } return ( -
- {sortedByMoveCountGames.map((game, i) => ( - - ))} +
+ { + columns.map((pair) => (
+ + {pair[1] && } +
)) + }
); diff --git a/components/Book/index.tsx b/components/Book/index.tsx index e649760..85bfec4 100644 --- a/components/Book/index.tsx +++ b/components/Book/index.tsx @@ -102,6 +102,53 @@ export default function Book( , ); + // Stat Page. + pages.push( + pages.length ? "turned" : ""} + onClick={pageClickHandler} + pageNumber={pages.length} + > +
+

Stats

+ + + + + + + + + + + +
Games:{props.data.games.length.toLocaleString()}
Players: + {Array.from(new Set(props.data.games.flatMap(game => [game.white.name, game.black.name]))).length.toLocaleString()} +
+ +
+
+

The best game

+ + ➠ + +
+

+ Based on the highest ELO of both players combined. +

+
+
+
, + ); + const gamePages = convertGamesToPages(props.data.settings, props.data.games); for (const gamePage of gamePages) { if (gamePage.type === 'one') { @@ -143,6 +190,23 @@ export default function Book( } } + // Notes + for (let i = 0; i < 2; i++) { + pages.push( + pages.length ? "turned" : ""} + onClick={pageClickHandler} + > +
+ Notes +
+
, + ); + } + if (pages.length % 2 === 0) { pages.push( , ); + // Spine + pages.push( + pages.length ? "turned" : ""} + onClick={pageClickHandler} + pageNumber={-pages.length} + > +
+ +
+ +

+
The {props.data.user.name} Chess Book
+

+
+ ♞ +
+ , + ); + + console.log({ allPages: pages.length }); + return (
`sFQ>B6} zwIV}Or#he@T4f4^5HTu4oB@Fh0*NvukOB#r-n~O01OwXV`hR+_--o7$#0=-0wb#1W zz3#R4@$cQ%O)>tD@kbwhG-ds_U;p^gM@IOMKKgjwL__e*!FCdOochuFuUGwqAJ^Xa zLwJ+nA4|3@IeBv(mqp%0qa{uHC*8lt5jC~d!zmL{Z#`*2#Er!7J~7+;)f}Vz1>b&C zqQa#(4(b~k?rt(Er`z@dBT}`UlSQA#TIU}MlyFw}|d|PN+b3*J^ z6Xs~;vMq!So)R$H{%R}q`n=j1JVn4a9`ZgDR33S`tuVthCq29E;0=@zJSFCZ zJqz>BHWOrmr(CJQ;1ln>rh=#b?@Rvga{Zrm{ogwIKezIK4)XtZ@kDi)SfNsIXbRuA zDsNRAQ{`&8Mb(N}XToldWsHB>GWG+{7Lh$U%sn5$ciIxlrT&!QP-DO>jb$vlIY(fG zn7-u?y@(obU2Z*b5n|Q1(RUeLQ`mBsbesnFO}>N){|B-me{z0|6T$uA{bk1JRTo~dN+E+r7!rF*hhlvfKj zaZBl5L57tpLT@-qPDt^^beS1(D{&ETW+(qE_BHN$)(jL*)SNRLe@Js*bE?(}SZQIr%@ZP1KQ=;;J(nAhJ@GzI zkg3TYOU?f(jg6PqnxrQ4142cq;@s^e<-MV)rG;oYmEnAy!hA*X65RLyxUIWX&x6R^ zqL&%{Ivw6E<}}iz>lFs0Juo&RW5m|v&f^%_R2I0>7}*-dD(|C3#;(>VaQ!lNrjHZ2 zQ-0r%sAMiqoeVD^VZUMJI&rR}RX+EpldFTRXm9y_HNsiq5~Nn zj^T;s)&Y{$MjYy*g2cJDam$UW86KjOmEdOjxvn(eUy#=7Ty1nGVR=oVs`Aca6vaK( zz$o;_?4>o8gXWYF<>ZAG8MlQ`A6fvI?o&nN=E&d9%#u7_KkXLE_I;W7^Ed^fheczSqbIk9>iRZ{e<_P0YQaBG2Sa#FBkUZO|4|gjGi( z_mNi`@dL~%6Gog6zM$?tx?$uJ{M&1OC$4*t1)mvh^4^C4F3Xr33M%O zJM-|Eg7_Sjs@(mn2k}iiMp>*w3z(8Ya==i;qqt@s8`?(r)8dHV+%u&2wodPtG8Y9_ zo7K+4YVV}JqlnO(F2G;A4?1nF2d1sDWD9RjYub|MqJ1Oh%_&+M@dH-oh=o^XUElS? z-M~+fs11JJmV*z9EHRD{U*qs~bZBmutX0$_Y=14h5vQ7R1YB+rLA8Bi<7Jj>pv*Zu z#5E^QTnlaw&J}Bzp(?K4_pZwU_gRDwrFXSS4ji(vvZjiG``=9yoFaI6wYcdp{o#dPanyG;tR`zHj!i^Hb;S zuKLP6GCK~v0r)WY?zD04-9F;=WJNHoPtr%dVoWv#FOUS0KF_1y$~RA7_Q0zi6J&^1 z^~q_P#RT3d&Yg|tOC_BbIo|C%9TF~w;ZwvN{s^t93>`gpi_RV70*3h&@On%NdVj#v z1Mcs$r@|r_<^yAloMqf=MZ#Z_r`*X*;liasIb(z&e%zMfINYqX2Btky$i&?A4UN;Y z4P<{|L%I0tadyrdHK%b;DpCGC$9w;pE5>dmaLrELXPCjl@YZhgXO%b19zW#JwTolK zk?JKHfECXW?@yPB>{iXRPHbAH#wQ>awFV=Aq1^Uf*La|5KPz{;$?0V{F3!09zI4dk zXp2pKd=i;3dP*D!b8ADrWMS_O{-hm%rB*08HTU4zR&UnFQ5$* zDC1ZxFyIe4HoX`HzKY2tDZ1=r_wo`i+mcrv?B1HdDtL33LajbK3P4q@Fa?*o_)1iR zd068ORpF+h?8F`q(YyojijLo7@5eqHF=Ab4R#T^o`Q3!JUh`3LP?Iw?YMHQ{5YX%&#=AeD$JMwg+`+o8IA zm-(3}$%8;M>Uw(^(pOYyg9xtu&>+mGFV+cED;AII8Gl{V@kOgTYb z74x@MCM!MGmMr*bm8Ayg3@e_5mLiHF<(grl`+)HA$rQrNV?YcbhKkXDvVt{Q6@c?D$Z;L=O}0zgV?>0%cD+ zw2Hc#fy8UbD)<5Vob|93iT}w+5GoMw?weLE5SA^zp8Uz?`mM1`l5q#${_(1MG5$-4 zM7{pmYls$hQ?p=<75xFjnxLj>j!!RIjTUS14mw-ieQ8%tGN%6T+M1y7rorMrxYE_EfYKPdxPN z-z3%i7Fzx1c?au}4-S}lJtu=(VA}~yWn_Amnhbni(a7}djwXpns0qhMEd9c82!JY9 zb#qgy>%9kTR<5;K^{v-==-=3V^W^$G7yTEfXFm+^(cR@XzP}y7o|s`>aoeIrV; zs8LuMlIraMnR2x0{0EUK$ziTEHusL$5-SKx%v_$iJl%kaHM4AMu3r)^cZyN}g``2K zwKf(~Fsaho#-828I^QXA0(+08On`6V1@?N-F25-!GZ~-~U;F(4R;#*#BFOQvf$k@g zK=Sz8aa@FcIKZ}PJu&Ba%YyVbnUh25p-?!z~9(5%E++|~H-??Z0$ zHc7rQ!p2w?m3HBmN(6UCxhO-ZkhanCuA`3-n(}8Fa0~V~Wmm zJw?s_+r_ChMr`ZgzW4ld{5DdP`E8=9A?JwIAd(x*fQO$Ycuo;GkPvTk70+3cEUi}U z_*;3~{}rWIc2KmDzPmK?MdTevoN_pQsfE|%Dqd4wcU1JZD7MY039ZW8lI^`Sb6>}Vf*1E+!+Ml}EARG;eZ^LLOFvgtZCSoJbDHIXztO z8|fQgWzf&JQ-#Bw`C$)D#BnL;YF@}Jv01|7xxe4xX(XFQE6p%XQLxeb0TKwHFt8UH7!mtYXsN*{%^{|JqRcK-f{Nc zZAVDEZQRT`w3kX*(xAvJloVg%QZB*Up^VJlUE1K#r{>bsj8`q8x%Av@5t=SO$}bfk zU7VT>9rGZOJLpGALQ1kw<%hD6vDSC^iC7;c4KnTTx;zbim={ueETb`Icmf^QG>tgO z1q?Y*t&OR&psy@aJ-0!ip6DnuO_)4lvJfqA07Tpu-+$>i{6yrD{KA>^Q5aqvJZju^}rDz zLBPW3crVGvB&(#NZO)z7=3?Px5sq2>spizBS3#*s}je$V1k z#+?P$M_#Z(2f6XiS-7rr%}Z(<{+r%!>7X#sznLUgCM8!_4*S7+ zf10;gv=|`W3f?F*>HupkvjD-U;};5hwk$qSuyW!D_^gwWsFkIpkK`4$Q{s@D>9jpx zg4iOj2-Gn@985N+_!#kO9pM$_R50q(Z5+rA%U5-8ny@AsCS+Y4y++YZIAj@gtSD9J zaf$?)XqYM$N!gm@QRIjau3qUg^fQ$e-6BRx`ibuAWdf5PMRQp7s7qA+0`ZAIu|XxD z4GU*U^L1i?styfZ$?;FHh09lB=AY?Xw;oPPD~*%BM^->PEqKC=vWHHg_~&!x78 z{dX``1$3k~H%ee;Os?+^ll`g#g<(K)S@h4vn}Y#KqH9eyE&dpEBkTVOkm*?mp}JUw zX-!t1>M=?tZr-H>V6-7#@&$l0*RGr1=zqKqx`qkn@xvqC@YGL~4@kK-xMpvPr9Fmr zzpK7^>ta-pvNn{Pf2GWAX0ae2=e67@DRD8%7Jh~~Nn;dw8Oe1^rN6W8m68r6o+=Od z&xbkutHN*;o2#guXs0ZexGyZ0*a0GG?FRy)PQG8%txfMQtZ4vY&uN6vS9+!;qUaIF z`GnCy)XG}YM}&!gt~yn}Si*k|1MQUR5%mo<-0Sie1Vm6wiB^DqvWZ7$OeG7aj3i7L z0w-u&=PMGk?^n_{Pq1VECGxb8M+V#O0M)mvk*|=cmq*k(HP@ZQ442d%mUXQulOA~2 z8D=ty%)T3UciE`_)_ocGFE~c9#6beDLrDnUU2UkrnigRY;8fzU? zX|P5(cp9=*HWOU@m+ppmVGE@B0cZL#VDZj3lW=E1)Oo^Nied zGTz;1xq<6H+?3;0bk>w}%7;so=+Ss&ZJCH+fn>A2><$)RS$_zffk}T+F;z|w60d6PIG;sX| zO{SqYWGt_&II$FFU^Qv6EOHRYo83w-g+GOX*sY|hG>}425bR>o?8SM;IgSzi>CUDo zQ@}hJc;GD6q65>NLk&CAYo^Um^ch||2H{1S*e&7YsWpgxOKKiBZce@;p; zO|9*?N(lovuxL(>>LIHKa~EONs9lj%L56VRS9ye1g`TfsAfi_^Et|x z6-G26h4}ILZ`^!5a53;=R?n$AVoH+Yw!9k~>E411gh!VNAa`&2T_-rI*&$&nB-@;W zz3QkFyXIYVJjGf}cw7EVkq{`rd@wfYaj4<^XT0O}xHdIxC80E^w$sTf*#R#s3_(~L zu`*39K|HFd64a;5D`;|0zeK?gV?l?Q95g-!8soV-PgtioO z+ix6pQ_OBD+ihcF%)HtmE$9j*F*H6lRPdJWyt>o=zy!nuf^Q$b3hfWpgY<7y+8fiEO%`7wkM(zEzm>d6HPy@!T~ZO^NgJ zpksodZ}$;U`rekTG^(QWOOe;(#4316jDP`mz2yf)WZ9V_jjP22D8mS$MH!q=VL8xc zeb0hBxzz7ID_Y{c-`x!T5L!w^{*?u!gP#E;COAoUSZPGQ>@iT_83tF?A}-5%P_pb` znobhrX^RX!!Uq6iSo<7Z9>baDO2kgMD-sMj5ByZpfxp}dp^(^?v6iG-y;->x$~XFdt$1tXeo7`y(o3|w47cdjl|V)t6amkZ^b>&_}qdL$NR zCDG4EfgSP|RW}Dzc>L?uFit@)cWjrv-AtSjh}laAO3CM%A#83V#!jqAn)P5-K7~T0<~<(&|36~c76HhWRiw1 z5g!FSzage>iO&tT395fqyu)oHFU!JJxtS;=9c>DE2OjI7U2n@y zuBew^c%|+9#`8_N#@X>3V;Pw+{k8bJexn{(s@MhKu_1oa$uL6=UCYO2ixA(z)*ksav=DIqv)+K4bKP(cDfc0US2Vv8xcvZ*A z-K8W;a@!iy#>?I&Hx`4!HLQtb&t595EYd-ZLn^go2XnQ&uBL1I1f~%9T9N!Na1oRU zS0AkCKQSi@Ql$s9}EnH>K-T3hUi3g;D14Sb>eGH&< zfP-tIrj8O30rP5=qbE9#Jz0k@76oYM5SaknFM zb;&@BnB0TD3t2+lF92HPvViJ@1JeG8c4malG@D=V3!TdZ!W*SCM+z~u24%{-d2&M@ z?E~ecAl16+847-RI!AWJQ`RC>$t;V?9fCAA>a$>F%&N&YZqEJhFhArj$f)5st>2;8cMCxxvxtSWyfP{WB`XyXT0mV zTk9+U`k*Mz{Ij4og^R2pj|UWdlgq@bfe|=%DEqt0%)S$+%DXJzeWyMV@zo;7T5|jE z+LG6>$`Pwsq})K;&|BpZlk>x{bQce@YV$tpkE}l4Gd08L+D+oizxt5oNs0W@kRK*6 z%i$Vft=5$c^J0-n8^h%ADqse$0vgKIecWHVY{mX!QRyLCpeBM-UkpT7(KhiDDNROA zRQPB_-{va}$mT$Zry~-1SM-feViZ~LoVX$z@k&E@P`?E_b|}Nv*c+pM5-)$&hX~nv z(QTH4}#Jk%#-+)IxwqTI{OD>{>rqbB>p0om8Vm<@w{LQdvn z*_;}wT7*7oil=@-o(EVDq4|aVlbCoOL6 z)rh^9{uhY7eJ6aFn7wr~;;zldpN~v~cz2K>#vi_YFa6V##ql&_*GZ7bvJ_9hP#y4{ z?WhrS=mpw$mUmZV`-b2Y*Jg{9NqrG%1_Hri>Ps(tgvePkC(4Lu=F`R%V)xTL=`m33 zZo38c^!6_8e-2C=F#XmB+Scs0NY# zJN%dym#860|FJgJ#@F_0)XEZ79J-7`S!96fi6=e%M|EBnIwCLIOX5`iy6x(oltLbs0)2@Kt6yr1xg)L@aN$2QmbJlr-6Q8{oRYc;Rm~VR7SG# z_j1s+fBYTBv>403Rxp-Wu-naLyT;IetAHQ|Xw>d?1C3gfFXKs=_w}4IPr}5n5T7r> zdYsCGKQ$#9nYn7Z374<3g*vf)JpGLEWywV+opyc>ef*zFLO9 z#=Qt)RodCnhdEb^lGDuc(uO#A+Vr}MY+lE$`b zQ*aY`WWjn_dCXoz!X6f>H_d>x{hFVT8lH?uNsyLNKrO)Emh7+5Gm0J|KsMuMoH%aC z;I$bLEyVI}Y+zny{D|K&lEZ^J11FUfU_h7%N;i-kDHGGA(Nua+Anks2_`!&S*rygk z54Bd+rKOyo-00T%}#mx{7*=Z!S!$l62%ys1C>VPWTnZPvR z7;-f&ip2jwvaOksb+nEl0l}=j`G-_WLE5Iu#Pl&>r6iDk}M2XWq zg>#W4DvP`R1&m0|w`MOrQ`Is8YW*MH4T$F+$q#gge5mpn!CJ;!n$owfe z=X?_K97dz|h9ld&gSGcB*4urEKPNT=n#@6^x}bbxx|JxxryLw8!bk-IBTV4OZRq#< zh(R%T$^NE_l~u7IaVg}i=@O6<;&J03)p+| z>9T*QmqxyO41`~p9^diMZM`;-9o|I(jQ}mTg*mU68tb_YF$^?v4u8!@uHzri1zKDA zWEbcgX+P=D*V_s0onoGF4P7eVi9ZVqdtu!NyZ4D)3UcC7xN4Ir0YHCPha8LNhk2Du z7J%k60iOH}i6?Iwa_Ks)2>AV^}@CW&v_&W&<|wkbu^EgM0gX-JAiw_ zkX6fvI+AeJ%;{xPe@#i|^F7VZgIs_%5{x*CxdF2>#kb}wlu2t;bIx6llSAlj4yYh3 zIZ}LG#~lxz#{jx6R6H0uvZHJm$%P1(lTMo{)^)d~^;D-q#ON69rQo=TT_5YOT!_jw-M z&Tj5tM~W|AmN7$GQ3nuc)2ngj40tm~>M+!e7Y(3gaWJn24vD|b-Y@%Xaupo|WCE&Y za2Cp3iQ`CLtXgI&ov(o?hF<}Q;`4zC#5*eQZjsOepp(L%+vgo@;?2Dp=Y9%GB`6m) zs58Hw@V86j=X@yFUYY}lx?zct&KZfcBJU#wy|RK{L`&aiQxbWe6Bq#?e$P4&+SvSk zhKwfv93yvq(zAHF16Py0XuD0d{(s6LKbB()(qUR1Xydl?Wkx_4fjA8o|4`$986F&@~&pziv zlf4=uLTKs~ZEXjH1XV(v%52F?p10(F^?<~|%8?p*u<7B(>CLEXWi^N%#mP4LSZ-Hg z+RgP<&M{RtA|UOwsVF#7x~oNN>C4!^?9EW_P3^ZYlsI~b{;CFdUsB_K$ceLZdUeWR z86>6ZNX`6zfm7sZ1)|PB4}QM_mUHE4@z*fN1I_SoF}c4^kEI+g`4#gu2y#H5l=X*RS5C4qw*l|y`Vl;N@GS*LMfs!|FuyZH+Cg(D z{|nqUd5bvrdd-f`U$k_#zUqbgIbwii(iEKct{M+uHyyvspN`c-+zrRuYJgp?@{xyf zAJ2apdDYnU5bWk9NC03M?){OLfvzRakxR1AFtEwD#dllU?o!4o)~G`#Wu+;`>v^Z; zU1An@M<*}=ocG_eMStZC6ZSJ)pia}7S!-@$A3J(W5L;W=RG+k(`S{GTHj4GsrE|uVF^$X{R z0bHB`X@8sptyr2l3t#Me^o#26R(-4RhKVpuX5~EFarUQCZr=d91!X*H*++3UUQkcQ z+AJhvA>H!nU_+ZePG#7_f(@m0i{KNAUl=l${G9xHmRvfVOwppN-7DqX(Zb8`1Su|GaG}#WtYy3Y#O_) zbC$7^nO>rn&bjF`g_0U5dRR%f*@Tkp{I5O8m1)G=G+VcqIxU=9i`L0wM^}QMA?1YT zv$+Y%us4cy>YJYPd5*XS4|*bd5&AlV$(I?HF5=}|Y8~Flh^e+W(smTJfc*CGZZ+%P zhKC-BwxTA-QAzz{yzW~)pA$`^!5r3xe?NT*}s+b z=C$A_y3HZLU@-*5AKx7DL6U4IbaXwD7n|Vh?)Lovjl*Pj60QNY#}_|9PIXoWCZZZVER0c0u(^wNi@l z)o?t6|62`l_sr?=13uw3ePLemnRdB-q1PPo8PYwVK?QWw z=V=3mzN6tC^(0mVKH^Ek+FG^14f_{_E!f36sv1!uvYXMo_`|)ICVvd(o$8Q$R&PHE zL3;_^NzBE>%}5|DFu`wP0+m6mzn)CZ6>HmmStdnRW&#QLGC&E2ULKN4>8w@J6$I@y zAqE7ai#KNt+diljJlTjuy4$|V`FK~vc4>fiWwRfk8^gsL(CC)n=$ z0hMfL6t%dZgg3`qU8XE~sBYb8(rw)k9K@S)wri7=%Cq7AM=r zL3Z+G$7o+hl%VCJ!knn5G1ud2qM;dwJ=t9v_Jc;*ogWIWaZY?4yP&2#2$!g@l9e z{Su9+n-KZ*j7@sH5N!!MF=(OlPjLNkOP3oR+%kc42u>d$f1y(a5&emy5fdVuTV09V@xT1Yfb}+?&@h6>_$HF#zxzVw$WODCW10JupuyT|93At%t!NA10$=bxM>TP z<#*VWv}|&1x&86>%8mN}J?K%c$jqsR6+ydJ>Xq+$y$*f0`i~sfiDFO77NN_mX572$ zRqcQAUl@TpMcPD<1JN`u^0T+_tRaHPWe#Og$tr z1#PMkEmTBmWZNw1!w7LTwJrAm+k+k%1Cd(n5&y1MH4Rl)DjU8=B1k-XqMI*U6`0{znNuk5+~zYR;Wwe!I=-SY`i zj#TFYRKfC8H(QVV8hYK5TfW!)XoKPf)^;>@@8Bc`+;^n?zc(%a*PsLtZT#LV!qr`U zAQO&t7_5808?-bbt)aqydlO?el7I)z;=Hw1K46mA)e)1#U}F_UPzE|Hu49<36x=~+ zcglBl#c!iNlMCVth+l(6=74|1uqqW*&cK$0U8XL zf^nO52b2L>%J0J0g)eKq#vPjBA>|~N^0{V$03ud;M#cXM=vpG-d<~KO<_JP(VBlSY zv{w7cXqH!$Lx#y^%g4vO%rY9_nh~26bbcquTa3zvf4OIJ*clFg%+OPQ7{Q|GjPp4> z16Ka(*cU8!T{s}fE%G9ap`BI;#9WrU-v>~gK#$U?`ZN}_@oiac|2`SlnFoxR7Z+sF zK)HQ?G>Pi^d1#fdjI{#mDG|(bY*3}lZCqDvS`Dh%fEN%zUezx~w}ma1 zL8By(T?`a?BH0$`CUrn^c)(cM+>@ zN|HQ4pz`_#VClV8Pw}X4uZd*wJA-ZraF=a7u-NMQ>Ux)jnR!`UIU1J6t^%9X_6loY zOV_W03*s&I4dsL!O+P{L?4_KlzJazq^!jUYHbxLKq+Hb7Q~-*}U}%s|#S@B7@U_y~ zldK)G5mn@?fl$<+&JkV-RD7!NVRlA0Y2&i`NJK>_s@ge=^RK`OZUc=ekkcMrHKnQR zh|sVe9NsE!f393eJ{tK6$VUMWv0gnf?AP-{tDsECFWdZ=c+$UtqXQ5WcE$HCP<{Lw zs9l$~{QO@%xK(ck{alQaocccea^hU~gVXMjiV3w@%-c|QQVTP9P*WSanNWaCT8G1{ zevj{Ma$wz{Sp92Ko6~xV##!rco-o}&b^H7Gs=8q^k7)x$Lrvl`yZgVLwToJ|5Zk>F zYgXHR+onyimy+?Cn^F!qh8;-&Dp5x0)p2>!JyC_8l~>`K@BumPok;C88wLk-3m-di z?y13p@2|$Cyq!_%3U@TuUEAsrCTNlfr2Q6HuKWwrF8anc3w7nktV1Ygo24c9or zvOE-$cn4`|QgUc`AF3vBR6-cidH72vy*vp3a)h~an!%d$#u4e0?*_tt1aaZ)r8!k? zU=&sM=%c6(sDMKDuvQ_fFoU>Mc7jHTC^E#0~b?03l4_JEurVOw@bq0D~)n+@)FQtUY9A&l|+v| z34pS|)K#ivB&rr}BWH16H^P9y-)zr)o&OGsFmF&h31To-Rqr25*CXz-=(qdY{X}H<1`~2;%3RQ^j_=+!!TlAC znl_;Mq5#`DL~yBLy4^A>aiWvz0|~3v9N1)7MT~gvt4LNzcjy=21Tpjl$fcd20--VW((cq`n$6wW;S&cxuYV$bwS8y zC{>0@H*PgPW~|DCo-X+TCRX(pN2O10A)W_}j!i|{MCMvEY8_P0mx$MxTJ;~4So7rQ z_YG8tz~Q4py+$h%K@I(FvELEr`b5f@OkSFe3B1aS~KtaU)~w z^-azNxK-v{5}G;41%P(8vY<>B$Kr3qtNU!aZ$gceL^h{h=y8(+a7w&6m=_~hOEzDj zp+nI(80i^U(@-r~8|A2n1(Ptg!b9~Ikj>F6`r9*k0?(}=>xhkas0Rh(yJu;7(iw__ zmF!t27XGUO;KiN!ZlQjiKk2rS&?Bmu1A~(0fpHZQzXyolDgWAijF?LGE|~7;sHd)R z6v@Sl+BVkRcaOQoO$pj0DUR=PeFC^mbN<>;KOf*z4niaerxFj>#EIZgLHnA+Ch2OK zKCE*BIRTtxOb-OzlPt-(#@gg^7DdYs5P4`5ZaeaWgfwt!CO(Ccj#T%>r*q&kA$^n< zOLJC>ml{?1#1M0vmc^*q(39A-z!@ire?V%YjFUMTi&YO#07dG!rakI-S*H^(!Ec5A z<*p<8<3$3oqETcoKm6w`OAp*rNK6r3(fgXIK&%U^i;~yk`=%^Tnxjq~w`Yo0+o>rv z0V#~G0vuG2oB)QYzwjq=EOw%Vb&hDS`WHB+|MO00D4z0`qWa8xnfKBSq(GCS&g|x0 z9)d3ep^Uu4HKzR=t5%m!GX(FMTnZ*j0#NJQbH;^T{yL{|Ho|D{pzA$yk^UspwF2hCHdo0P7Y2|o&a|!wwAe3wvQ8>BVmuR6!8thV zBdDG{OUj=5Kd#s73KB-7CHnqUe%>wW+&B@``Q2#q)YF4S%^F&yN7d6rw2G+o73ZzO z#axLq9S7Z*O75-KNeztmM(T|BhOB^UAMDZT#Dx9U!d)L~&s~4prmBkW>vFbV>|wtS z7N0O@FM|%cZa~<(@}*#ky42&{QO&S;#LKmCy~~b~pTfnYtJk2)Jrb1p6z;?;nENT; zy3b2DaJ(9K&KV-+oqLBDOnK8Ss%c-bKWLr0n9>S5U%BuK+lVEdhpF_>ub(`T7y?(T zXpf;uvI>osh~9?0maZ|Ytb#sUy2f6VDb6LTn6KX-gdigir~3TU4{6o&bHur|qx7z5 z4F$iKlvg65jKy-PJs}}k&7k!Xre}4@gz=9ZmbUT+2QElew0%Pju%`0T&Ky zK-#51at4Egw>ociNQG6|-_pt`sAf|~EDF285L-RR14o56VZKVQ=F^CrTo=pVHYUeR z>3~84BI^gx)c|bzw!_-{z$D=Ke4wSiY1P3TYbv{%$zl{E@2+sIyhkUU1s!0!yZ!%C z&vOMMta3ttd6<>tj*NVg3eb913TF3MU;baZ--!;;mk|3fy$@$lHtdyyI=!#>_%F(h zNzP1fK-Xo(HNnxfXok&SA2=hkVxS406CPazo8WmAZ=)!Zgf*>i`pZZi!gzQAM4kwm zChvVe&3qMX6U(r=CSuvfwcA`553b)4wW+X3NjU;T?aX?^; znwOaRa!O8I;xuUI<^8)$)kv_`9mhA?uErTJE>LF>byke0#p8;ivj%s2fI z?JkIaLvW>zBkT4!=}> zBd!28_*Uk;OHRaM(vn}Mr&o~*woVZ50ER{2G!P=b+$PtDTHTxdiK2F;{CyqSz+((F zp;>(_vOZ$C3Xug=a*GIL=t68PY%&e2)?YTJMBMCvRnijSbQi1M;xMWwy-U5@!I`vJ zVWg|PmoMx=Q>FH2vivL|XbIIRL&DQxg3}>r#(6K}WQl@6u(`;+_!#AQ<(eo6aZR~^ z?7@ZAW$HxGNx%^iE(}r}3<~H?V_*4I{EbEpCT<0NrTaz)CKbMoO2D&8g-QKcs$2~5 zX7LB4HN-O{CGk2i{8wtjSAYx}L}VSy2hG)y0?=cg!>hT1=~&N6e_UTYFzb&!minb9 zy%|n%h#GDt8d>Z+j7euJ$Zw)*PKqDK2^2e5B~4PLhvrEG`wATh`LL8A+39QVr%VIb zTR<*Rx@wS0|9Db>wy`xUGtkU?P zEx-IjY$T5ly3!8++-7||mN+hVq=LIJ5!wr*UgWlLig4b}f(rWCjgD}1Nw?zw<4x$3 z(#1wBtF*7dzy;HX&`7Li>X}Okv`nzEVPo!KcLtbOa%b4Q67aq?=V#+$dS74G7zpni z=qd}h47CiJ3g;_I;8Ym0Ku1sj@)7mcAfV5sNJd!H@;%NK1NA17c0m;IB`n(H{z+YR zZ(@{x>a8uKl2Zv{M34r{M5D`!J&R7p1Ns`Mof|1D#``=mo=|FEo%V%M(ZYp zubE=K=8EH~ti`BEc-D4L^+d>QX|2)L44V<`;4lf9j+4=)fu(ZKKqu-7~SKde1tHZ-PB1vAT3l#uonNb_M14Vl=gedto~2F1sZEh z{ioUb0~Amn{-k|9?o06Bv|H#_dd5b~qh2c$%&&ne=js!Ub7f#Bp~N#H@4V-9Zj zGQ6;%9X&OCT%1*UJZL)+uKwa)vgM9?UodBw@MSC3PX_qV-Werz)^q$pat9`9Ce8q* z@X#?~iuO(nPSPBt%R%S3X4=UPCnHM3UPJ2Drj42W(#O{Zk`$$Ya42TWJUz~Ocrb_l z*t`4YjS%mjO%?$XDFoX59(Gd9frD`b!hpuhpy zd2m2B?sHN=O0)RiP5tH$+e_se_Hy-JD!3nDWlr)v*gs*|Ha;xAsB62l*mVI?nDU-} zBAgK^FE1y*S{fWktFC5a3xjE6w?oR;kT`%3j`Izfc9FSZVFf=QreZ6< zux&$F>Mtq}?7XB=z{Bwm`N2(v8`b+A5GO%Ny#%*K=lpV7M4b!XEU4u=Puk4ei%{B0 z`&IW|wvhB-krBmC9>$5<#nV%R8SFo&7L?O%XhY)(B1svFAQhN#lQH&HX{e(2g54Zk zb(E8eU%MeS`|TX>-Pp3o6rc$M1ITdqA!liQdN&uSmgOG_Ui?3xR{Fig15qjT*H`KJ zE`;XW#%wACTkLxpF{-2$>};f-68`~1?1$|~lHb=QQ%gw?j4{1KbrjuhL}_HbqsGt` z1FLVa>ON@x0h3btBvVU4JaTTbbZ$a@`4pM)fk3X`2TaV&aVTbQFyVl9{63{&>0Mmo z=yQ-H1X5ppajmin6Y#V#n5LfpFljS=?I{>y_39^d=0CvDtm#u3F2*aRfeQT`s)BeZ zO*Zfk2BA zh^7qHu}Aa?l1K+dtdau}A?gh{B7Ygn$g#hX4s0mw7`;?95Rq=nzjUQHPv79Uvbewl zbb|f+L6%xP-J<{lz|$K=e5Zo8W^G#=>y4;$E+=>ht+#2mJ2;AvolXi?fe!O8R=mH` z4*Trk_&FOxA0g0lLk(1NE3Ey1DJ4s0hyVEb>Oa=Gq~3nJu1*=hip447XL5}%9pY@LUT@kaip>xM6#SYqN+s2e|cwh!wwS=G9#jr6iGy?0x))1N) zr9H@yaEtXX%AR^riO&l#kh;%<7->GiYsQ0I)l|yGyO}@)MtJ{D4iOI+K*iJhE)86W zrh-8LnF=T8DB9aX^%U@K(PKotY;8< zKgfN!>Pz9nRnu-!AJHkkJ)hBp^tmcfUO9wN&SrJ~$O=WXz)~z)ED>aWD1;#svy10! zJLYS8h>>IL)(>ofCnfm~s@Fh!th}3Sbl5U=mIUWM?N;jK5A=0~r{g=0mp+2pr=E2z zHQm41Hu#|EFE@Mcj=aRiD*6RV!vSL7`2tvAsq6#RrwS4`BKPjgz`&pG2>N8zt>uF| zfeafImB7rLTz|M;^)swNBg#5l$5cuvk4B zY9k}u%5hv8aqXQ!A^>?MtP2B^G3!;Qr;;z-9y%P%4x+9EX4>S&9n732(7qtwOz(okCYm-F zd?UnN-l(HZ$z}f0V8^$j_{%D*wk+<$mtga?Tt>QdSImk~u!|o~sg~i(^(RIyR*DMZ zl#rQXml5vc@Cv9|krpQ(zT?XlMzx2#|29a=4LHJep}4N|Q5%A@xaWHT_P~YEX5&IQ zdYu6f+1!Y5g^CRj^+f*-?EwB{J++9v9tW<#EW@hO&!-sBa>PFNU}jA$oLNJDZcWR` z_`tHJb{#)og1lv6wJT~NomT)G2h|-h?BBZ|7A<@g;ynTD2AN8$S3t!Mr5OWC3SJ-L zWxDxbZ{=FE3>&@6Gug$8;-6jYyhymTd5|j71fLOqf)y`Is18;Qo4%&EU5()j=g`PZ zgg*&ud}!+?-d)lGCg`|6QBUx^b#?aA2ts$>AQz%*SQn~5Cpxr>hhgtng+?F3$T@_M z_$}_8f^RsL+STm%9?4T!Wd3Q!%7u+FBXP@6z`?pkiWf0U9KHi1 zb0PZ?fJ7NUcJ?W zGH?Z`xWr?R>6D@elj=UuG))W$JY?mQXzie{%YH-wNvR(89TqS1y7w0GD`|2511Pel z)&X7CCjL)apO&)>mN;M4<>cO`?S4=!%@Y#wKfVY(LG_@g9EYY@z0$ze>D{gR9*Cbr zUZ35$qr_9A5Cl~^AwO#HgGnqL;P=CBo;)*92e1nl@%zRt_B4tIqnh4Rf9t7l_D^r> zLBL7h-4AuWy(koJA$c#;Cj+A{{Z0R20bTqUYU$fhK_ zN>les0z*%nYVkGw?Yh&W{`ggHx?VjeT(t486H%^0DM9ThK4jK6m*A5SVRSoXbv*YX z*dG+`vC4fItdm+JEuUJ?=^%6x$N;SP0I(9I16D+;+Yvc+2QJjz+T&RJcz><*?aYRS z?1|$qg=mFxi>{L*l24KTQ0v)5;R}l5YJgl z0wo^N)LQd4Xe5-yrgaPIr~hj`*=4Yy1od>s*2VqKRqD? zLGOfbUv&gCvnl&{DCnXLfg`X8w=~-`1D~y?xvbhmGBVQ!zvSi2Hp6E-yc0N>E4|(X zD};{;Xah==K1HV5ji9bYIbb+*aKqx8B)PtypnlGa!|QxqCNB9l7~SD3%X7TB#go2K zZDRcY>YCVqoCDveQS=@jzDL^7AZ&a#@W*lL?Qiz&S@Hy_e95~{VvV&0!oZM#pAhw_ zhh$?m{$90rqH?${P4~+yX{%ala4QUhzp*rC`~Ty2iez+8aHC!}u0)^UU+ucI4~(rq zVs$C1%qBYeDL7qQ4|sCbAmOQuW8#r_m5LNLt!#!T4LWQq%bGb2oBiCe$sN&nB4`b$n37_ z2yunHHFsZ(2fYwJGg!BZr==w;sZM+_2#x?3qEF(T^1^X{f96Tw0UOn*sXAr(SOyk_ zR9y{wQTS@^7SAj{7d2Lm(Azh5CNsG)+Ep6;8KLuVOvEYFw`uk^zj{a-;%{JLL(NdI z0mr5dG{8?4SP!PtNS|l)m^@A<7#>%hm;vNa)ropjg$W2O0+=pG$=rqb&1&YIPJ3CF zF@g^r*+l?rpJGRBG^?guyg=`DllX&2RvtDa^#RGcXy+~WU|XMj{1fDZ(6RS$(+Hc> z^t26WiR1nk-M9vB@vCp5rgg5xn>rNSh%@JHk^sd3t&{T+{##Vi`!o-{83@<$uN~%E zt~!LaG|7&KD1ua4vc(Eo*NMn{-;fK;;$WFB2T2igQ#PFLUbq;09&8X}=ON;sgJE1B z_2o6Rz18)>edV)P)+D)$d=6Zg+(@$iX+k0#XSwuf+PJm?^S$uiC76;YOaDl+V|wo3 zT5#4rU^soC!@jx+3^bg0yc!6!?&F_kPbfgdjgA2Dp!E6tnu~w@Z%eE=Bcv99WcJgFR3lfGU{lI5_ao@?+VK zXsw)M#1ZNwP{BW;?Vthgp*UgnQqVH}2iIvYkPo(-QJ)3E95H31&W~0L*WeU6SBA@V z)d&^hh(B|9z&h^Sw!3vZ?<^b4X0g6$mo!@6r-mv|LCJX=EgPq^n|T{gRmFx~HD=49 z$5)qfHM02}7xT{5bH{^ic1UebqwL1^PNuEq;PZuGGb^y?a9NOVgRY_leHpq7n*=xG zIas4evy2&F&*~J%hU?;&(%r9|d?&QkLyEf^@)7Tf_n(TNV8Lcq;-wrtfZe-}*LksO z{q`^0n{y^cdP)7N`Sr=bs$zx)efI|$g)T+EWX)@~wiy##X!8R=4n2=V-deWk9Wdby zHgXt`z1w#sYyaOJ=aOGcWgE`3;H1wA(8Ig2r`Onb`#crV08UHzKbp|&& z=02Dj4kOO#Dw|)TZ(W1q@FijX5S!*~)R}t8YqOAhiLcCFnp}B-Uy8mK7lsFh^^X1# z*8zqpWrk*3fTqm!C3d5k_kfKjh?gE&Cm1%5enf6C_-ChJB0c=;qK>O^5&yshT)2Oq zA$3h_hVaxa-}W<>l`WJb-Lr#Qp+U$Erw<&S9)9dX>-phjJ@Uw z7_Tqdlpz^TZeTaXy=Dzx!zeP0l^PmiQMyaJ8$^$$m*_~rZmoB}!f1TGCuINBFOocN zezt+P4G47GQp2u0-c0fsU|)dHW;lBQPDjo70r+yb|w)(EJ!g#t>4*TXLZXw;>r zCaQZi&4o2oII<&?(mV33Y;F!-CjEoqZA~waI!%sD=P3+@_UNY`vyLrm47w# zGyJpr@vAH?r@oYaz<#M3fe?A%S(&Vtmu>`&2F_j4&(-_H>)L= zzY6KdS0CfNKwI`@Cu+2q5Z3U4(>=90^Yc8XW$bqOT-QpDdLlD!DG~HR z*T@eHbqVkU{3hW1wqQuRe)awlMff#7on!Pj)xv}=1>2^M89;iTY4TVQvme-u$SK<( zBsGhxnX-yf5OE^%GT3p$;!4>Hw)G2br3kw@2yF3=(pHLe#**Qx3U2DTB0daW{=jg^ z-t*Oog$oRh~9yu_YRmrecF`V{hZZM%X6-|sxHUGdk;H0M0 zTOY|=^Q_~89khG5y^TuXY680@W zH=Q{pF8;Q0608JrQ|^tBuB)L}lS5|u&x2gRF1YQKdyJLwOR-Tr8a+4T-EF4^SQCsh zFp`8G))vJaTPMQ3wMKS_(=WLqBL+5D1Dj9U`&Fmi9>Ubxuu+qd+}Q1+w~Kk;lZj49 z2mCp?%?Wds587cG*!PRTSo=b;MJVCJgq%5F*Iczp(7@Z*BbB0sqHZ z=LeW&uP`qxPx55~i<8`lYXWT>sQ<6EZ;wm*-v6)T%GM0l%F0q%mv5fArZy{4ytG{N zbhdO`b6T0&WwvgDl7fo5a>aIBT5~&<*LBY_wY3uSg3636l`cyxFGw1gs9@v;5%|5{ zA5>7$+Ijr`qaK?fyg#q^>-Bs+FGRrC8`0C)n5Vg~@gU5pvi4nuAhWgC%yK;=zqpUs zj>HZ>mZPV|h4mM~*F51N`bM0QF}$8%q_$rwe93s3qM85Hxw3a?BcJIL%32U~c;koU zf6iH$=;PEU-`~-@sAVgAG~UgpQHqZ@eA_v9A^z{UMpQQ+J+kg`I`+?XXQ4wRA4rI- zv3ee1QNUkYqz{JZ0n?mWq?XLb?AJW>x<(~zI{5EzcH#8E8Hzq^_0XbuR)y=S2WYq% zcybPXFnliqP~*TuM!VoSr+?!V31Tkf#;h``@yf9^U?ow!+tm{MHQJa(t62q7#+sMF zB!%Q7*>}0sCgWQB+~w>tzp*e8EDtC6Pk5yItt#glwFnDJ(&Rh0OK&KZ-m<0Gq+pQ% z@iYe_mQG%3a<>Sz7;?7|=joowm{=S0jHa;99WTzeGWIr&(fsy=))h@0 zxSMptZ9o-t%lX4Z_+2kf7u|;6-XJU62E3V1Xk&qg=cY;*|OftO=rrqurRLG~s5V_0fcyb^7KqsXB$vA~4r783KoyzqgdVoU2G^`kXX z#n*v}IUC!99V9=6o&PZjlI`xJtzluGLC$54GvNw&G805mgO(LiIKsij7{SCf7#-n; zya5(@x7GSzhCi6Q9FCBgl=AP}?xv|s)X+mN-(*|$n9>JBTHIam+h$naa5FsTsH;%} zD^Q(d;4@07vraE(RaBIgY}JRc2*;Q)R)P!DZFVDk2Z*_oQBu=LdF9YHZ)~fgeh6L4 zThH9SNS*EU-rhwJq(b=;(U<*J40iFv3P$^Rb9^1uj{fiw>|W^8+T**2dvsm)mi84w zN*JvirpMbBiN>5*zh0KRBH$NQg%e>s1{xFa^z*6ziQ16h@N9)~O@=l7+PHpj*(5?h z6l!0u%Rvh3@|}_rIv9mY%r`J}uwF7MXXUCyQQKq$DMPA>i8`T78vn_ZDR4B~V2!%` z1A?oI$p@c!Q@)6Tdck5DOj(6i8gX&;W~A&PEuj|W*?BS%E|!9iqJ6V22OdlQ2Q!00 zRNvW%3*t(zk_uVJm@8yyPiU81>(KRH1UZPIJB7t3WaalMP#yBr>_x0QlVP|Kz1 z!TP&SaBBu^F_RU3nmztJd6d2o%++(RndSETA&x;0v>6w;v)nn%vimBe;~ zGUKoK2be-|38Fi)zO6GEXlAqr+~M^9eG^qdr;I6C@_2>=e_g%Dw>Ts$Q^ADkS|;rL zZT@22;6MCI^OJKsDMvYevWX7K5lHWSK#|u~Nx>-9cZx&w1(_UO>mMwdb8ISi(U*rZ z+~_@ds%Tu}k01#ga{WgVdaqExO9(Y}L3blJPmxr@RU?Z$YbJ~QbM%^?`zeo`l3z1> zGXz|UaWv!nxWdU-eZLa&@IR|#`@TAWlGkL5PQh@~G;nHYIOFt^e2?G6qrS`;-4&hS(5IH|1A1#}>fZZqV!s5eJ5lr!MQ9(<^Ffi!bPq`mf-l{z@@ow^tVL z*s|_!>}yhTf#uYlhLd7GT-%!XRcY<~stWfF&rhQ_PN7~jqfl>GokII%EhSeU%i?2{ z6<(?^X3ObcK5~%YTPWpxd)#fSWWQ1WfI{#%t=Smy}UAx(} zwl1z|d0&+lqPJ1Kd5}!5j&W@q_~aAzv8&6w&TGKVoTDB7c^^^w{Frdvcs@z}V`UKT zhV{H#=oVZH8AyzQCPg39ui{s5yniVyysS-fKp#oO@nKmbR7v=orr+iFtE8Q-4kSE` zQrM5K?w#5-6Gw9|i@$j5`Qow`|u%* z{Qp=h4p)dlJXTJu@AygH{?opSo2nNsIAx~msk^1@ZyuQR<$X4=c`3NDeDy!HBt(rL zg3x{oySEBu<)mtUYxw#}HJL(J9^?9O@0K?4_a_=G&AK8DLB0(r#Ol*6w&3vw1rM9f z=(AQsznQ+%wGn&Qmgb)GF9rId`d~Gqs$`|;uZ|#6)kD!E^PGbU)U8!^83c6i$nQJV znDlPd+x#=S8E&qnb%c86+WhHrkWoh97r|4DBfq$T^J(Y@-i%kXj8h)p`Ke^HsW~5= z_J|<+CeWjeI&+Bp!-4>STjH%Fd|A4Wp+n;KA|+@+^v@OL^|wlLK5&^`_^&9t+Z>~^ zNRrMnS^e*bR{I{tXxQcUKHy0i!z^?NYNH z*SO-IOUwGO#;^G1zB!kgs&=4HZIsPE=b97rf!ABvC8st2ZDL$!jh<6=%82pErWoE= z-R$JGt;u2a?1gyl_==Ub={TB|%CkSo+e)?ecJp-OeXHc0-s52|Y>M~8VJ}!OU%n3)b7Y-;Q_zK!w5kIT&9L3ZT*GWG3%UO;(dJSU3vv&{q=$3$$DSrj!OWfIT}In(Ed$#5cp=smBapcOXmFRK=oWD9(13 zQMDkI4f{ud{@tS2GV{afb&%q{zN~-MAW0}d0-Kvd>g%okE%aj#G2P%<#Ft}DL$+RC zs|W=fMgJ++?g4B6 zn^PAvUZ1P0S@3Cq(n6tfbYLfX$;wpb^;7fk@&7Vqtie(XJvQ41cAg-OLSyKa9#V#; z^b|J^GV4wxVvjBeI-TJt4nw5qSo%k}Hs)wr4)r~9uNO;O&QCwwkH+V@a2YnlYerq~ zh5d{_R8?0M{!^%EGCqNgHae)kNj6{J1;AjMYCcK%e@wr!jwQTx{@W-?8f#)cwm>jw z#X%P;PFQ@K{VL2m^LP6Rh^K2_}us$Rp@=R*oIV^*02>L{39B zkyl-7z(YVF%*}XQ&7R))94R#!i=c=6@$HOh!`gxoHG^_JpCV8BShe*2Vpf76u9Qi* zMVXJaA|IyiSaEz#rZuqwJ!k&kkD#Q=X5nIe-S&K=`XdHb5Ik#N2dpVH>j-%EqB}>?;6F1D%gQR~sWHKS}8)3;++ zStp~t1{!yGJ6h#FES|tC5Bh&>2`1Yu?*gcP-hTe(H!hS2-!p%)#GL;>!hD8fHoM;! z31iBSSoYrR(4~R)QJJbt2P!OVivf*kd@NQ(UWlcVfirH7Kq;8r^t(a2rLiQX`^w21 z#Xv7Ybdpq!gAwmT0+bNfyGhRzo^wDZ8kqF zR|UY{c*`~2m4kr2q|X{=xmL6!eTIpDGz06Fa^suNVNA>}6#tj5WW0dtE!^4Hz1m^m zQsDiJvb7eCt6T$~IT{9=M%;qBT_k$5#g7)c3A#Vy+s`s$gTy&vj8Zjo=YAH+EcC-) z_L9u3*I4rCN5#7yvw=aNp0TFQ+oF;rx9Q5WoU;%wS^b&=Yqcma>h}#_j&7YYFO#6k zRLO~LZC%R4t@I?#rW7CdAGXbV$2*q@3>J-(?}|O!ib7{XLZa=Zz%&ezM@CIxO`(5hl!S@T47!}^ zF&BqZz=QK(GA3)>iAzg|e>Ja@%)QA;&Q=jW9`G7~B@uVmYaTw&XMs_Ra(@pPfvc-d zVvyU(j2*NyDTLq{a5DFwBBh{CD5neexuo&uSADV|A>@Hl@Q7rKxu10R)OviV+eJru zPq%_qs%-aqP}&|Cb0}_O^)tU}SdZr4LW8^PIF74{Xyl*ZO3+Xkj~;bB>jMs40EF;h^DJ__7)4Mzz&nu$ z?qTN}YBjW(2&(OquX~Ls0coXsUP;(gWJdw!2|9<05kJ1K2r*jcK9w%q^vm(z0`|+g zGz8D!p~!lHf`AJGAPS|(6l|e1356jV8Bc>bK_pEYMqf6 z2N433;aw!9z{!`%ts<_6UM(?)4)B<@@31Uv$cyzessY{d7EAv+faBkPBgf(&w!uZL zj+wwkI5;0L+h#+~X5Pr0O_>vpMpA}__oNT0{)Xp8pDNCc z#uQUx-`P#}O;wD>N*H-zS2H5wdI%r%3Cm62%uCrKi7?IDzJYFmy4N`$#RtZbXJ(a2 z%m<=wq}v+Lr6o_M-ZO^@rPR9cPruq((fgIrubS4;EQMLwN@B8=E2r^Ja0(#w?%Lrx z`K1y|9*f?6GQ!`RVjlzp3;i05K!w#2Td0UfK>tX*6724k zV~lkm2^JlYX)8ks1${lF>0M&xiEH(%jG}$Hm)^yrzN2D43~+x&Km@IxPEckhzbfDl zfzi2*u=U-X<1u3Edms_wb!Pa-?_KwS4cO$Y#W`>r&Kk7m7=gR?uX~YuWL=)it2)~@ zO&CkV)-2YrdtZq4EHE;T8w+CpAxRyF`7^dTA#|@>D&SfjPO;*KnKFJrTomWtR2SFM zJ$+^|F85}zt$klYPtKv@*`G=Uc}I9YlJM88&$?D6`m%dWv7{%v&T4ARtQ|97P=+F& z^XeLo+ARrPHt!wiLQapCK){33mW8X9r1JkSy;}00qs-U#FW)&FS%rQE#07fEL=lAU z-Cm7ym+*r4?+jOI_U910*0XdH88X3JZcLBaUWN7rDN!SO{6S=iivcOUB-|mfKTklG zIsN#!adh1M@I#B?JN(-mD!a8Kof2}VJQG`iaFu8zlAj;TxM;FQ(~dG{ZWrNj0PVv( z?UxWIFm1yrunCkf-{spxrMVT9W9NAV}P{Qe1X}(GWAfKNk(n4@DPyPT#y5BKxW| zzU@6mGN&%Dmkf+5bK0vJRm>F0k!*)mKY{~0s~YKd3oEW`Kc%L{qUJ3pzJGoOgX|L% zi9}@@Bg2}gNGW@tNUV?)ufhUy`+;aXu2USWD66gy!}U#p*}&|0!cIY}z_<9!Q(sPr ze~$2&uaYObKNHL_rJl{a8sjO9vv{%IA~AvcX9@Ycf3C5tsT*R#7?gwS4u9I6(w>h4 zzhMtO=aHyE$e*prpzbdUl;<2Wn*x`Qaki2RNgylhyi}TR`X82VqvfID; zhB5zg$^jq`ti3dQ4mQ(;?44R)Nwo)6F+q1Zu$ma{LCTiiQ-Ky*FgT)mg4R{L_Q}Z& z=**EVAt>d2PZglf+x^o-aC&xo?mluJ;d0aZNvL?H+yt-HwioBcqcAPBQ@>Wph3jaI zBI#6n`rYPbB-@nGUpgG;)n_4F_lXu#{dYw|{M_X!llbv|kWrFlQUqJmM$rm&Q~=#8SO!8yl^50J3KqNB{RjGDL^RzO!^?OJj!EwsR}J%V zW|H=am%W2?uyTNIWj{B{G?k3HG(;vauqOJH=fU21WnndkVHixw31>I;Fr9Iur z`8DPPZGNO+nYt}@8KbP1BupYGj%7@5-)b=wQd8IQUyZxv-$!Cc_AfFvj`mMY{O@iX z#hh_=S{c7GqiIrxxG4>=Oozls`M1=-qAO!JnxeAVM88L+k-;wHa7~m8EKS}wFYDSV zY|ZHI5}bOT(Jh3==bvaLW19}2_Fv9=MASM_^8!`ZqnrEf`W$9*;r?dw=qC1Q|6KK5*n{;2V^1y;Vfm!Im6y`Y@2YEYin*-y?5Z9W3REJj z*8;QqJJVivxIe?Yx_pw_;)t@VV8;}SndBMjvEg1J=vbfdmGYrK(=+9#U;-Rmbq7uh z%xK#0v4sfp1vvuvvZKDXY0>L)rR@1bO-pe#YHAtV#kxyN_9;$%B=4#gPm#R&zvC(F zW(rte5#5wUjEpZ{Jur;1dqlihDNmo<4fg`0!OAZ~#Ui<)sVwx^%=tLarOA`I1xY`O zN-93q8n05y6n!@~ym%C8`nNZlV7#9pjl3>-^y2}+cfQ{{tE;-`G#Qcpvzf8i%olwQ z#$IZtyhSag<7*$fFXQ~#bk)y&M7-@9%Y-wpdtHE8(vAyf)pwBkLap&oJqmasHcpJ6 zX1Hh3BZQPd+U-59JQD2M`qqDdN-#Og5b5RxYI~K*@yd?yXKA!<+2a`ZKg?P={TsB7 zn4UH%lf_mx#ltbp7)Z!=AXs>;Z09gC2a_evnb`#ts&zt8A*><*qwU({hZ@~}jY8OR z{vBY;&TB)!&U`Be|J0NKm#?dGtbOwk&Z~Gd45tXaL}lt)^0H=(Q2W-{vljb}WoLkb z)AO=MR z$fgmBV@(TBAjrTg`l@taZ~q%5?q&V2l&D^y$aosXj2|#wRh%Sn@C(bxcdc)z52NNS zS5_WXs7-6*?1z6n&SfH<>fZZ@+)JDR8l{}+jW02`5<`VSQtd&#q6EwIgNV=M`UK90 z)FXC-amU97$BM#S_w<|bb8QYTncCR3<$LEW=Bs9kK{Qdz1iurb2t!K0w_`1?YR<)n z*xXua3?m!e3S~gcJnKfzYy+3#n!nW}{*z?orPW##d=9+YmJVjBq|Rte+@ zyOhCpE%i?!p4`@!K4=Vab1=k8>-{IrNTiXT50EmAlahY2o|lcD5~b&djqS=u@E>xK zMkLn;G@qB#d<=VKv1sH)>4`D&0QXd;LVv2r%%Pwr#nf#X zucBN)I839yOZJwXMpnV}kUA53$R1cv@<;?EbpA)wZO7r$u4}V&uQ-p*_afnP62NY5 zPI&H&#zggbn=cD~c+9%vZCZ~RwFN&N`R;_2e^LG7MF(#WVFAPZ+=-zU9bEp`dtk9b z28)$060TRENbGIqM~};X90c%8=~1^i6G+XmM%@q6)ZUQJiay<1yI@U(U0>eYw_e$N zTnMQ>48|D~B}(u^SOxhK{@PX9IU)CYPId|v8M3_yR(;~pa{(}#L<@V zm^vCeOO41g1s6h9R*QeBOVaA(Vjc+bRuR93{w!XShE#8SwQ^IBJ;n; zRh8D5-`;1MigHDmJd)X!EBV_y;bQSL&B{X;6c|ko)o48w%YYJ1oVo`975B?Culphr zt@~v&yFaQO|7f49JjWGRRBsNxaUrT)!GSkRv4o3-FA5uh*$hF|hwcKAg>h3gPp>7& z%-PS1eTKhFjk0y4%{HhP%zhe30+rh<~I zf1u2p7E$}UGKfg3%&8%z@g3uwf_MN~7QJfoA#u8q#@GwI#yic929J%Y`0#`v)` zI@S&w%Z|`)d>~+y-G#O)eN_%4Hj>VHz_Ir~CFsP4E8a0?T~)=Aq!g5CBdd2W;@#`& zq?1tqETs^ggSpBP@C^NHW8Pi$N?_?}^J`Sv>^Y&MxEaU>+w$ncqO9%_!4 zOA&}3xi)QX*pV)8_xgQOPLEG%{7D!RW%#-K2&-$3=#ytgN&1KW*iGhf2-$du;#5of zV=?Pl3D9t5COBGrL3zA-5H|Q zVBKC^GCR8&y7AuK5HVh7IXX=an@we#aSoV|$3b%sZ&r1@{{+#gg=aZc;GAKluQ6Y{ zV($H9BL!@tzD4(s)lVal8kq`LbFR}!@};S{mcbPT>x+g_3@+@(Vj;##tR^xB2yXO* zw5?xUyNG2*GBi(H>HD2A^pa?a2A#P=y(jjW-`|tWM}EUpzVD`JSN1wVw)5j`p>9GS z?-l0rkTR7>x8Dd$q8*K&)tLM0gZ@I%@F?4-bVr zuGDJDs~cEBQ!XK5zuGvCpFa^u!K~}(6lDvp2spxJ#_oCN@O2xeu$7oacOC^U@h64B zhDZ02o>K5_%14f$YHSvEQ~lr8uwfoSNTrj^}g)MPDmk`LJd%RW;6Q(kP{z z_R#sHC)QjIV&UaQg93$KAd-oc(2Ur%X6H>q@FnLrGy3(dO^V2nx_Y+`MG0QXS6?Zy z46Pn}1M4w`DIYTTRaXu22#zzwR90OkFKMNEWJz zpn{TKZ?8GrBkPg9Vc`sAf>S}+{Zr2Tr@>K#8|>+aC2;s!X~b#vF9s4N^(IErVhf34BfCs%yDDXHiiCcBRCF+pVlIjrWmz#{5=tsYZm7>^|mWMS-B zdz;}}q;)B*$WCg&VosJI(@EC6zlJfiVXkDLt3Q}J^(xS&$0Zh{{VN)WHa)Dj4!k|m zBe}Y@X|1$px-rg11onlZnuO1Tmz$PgN`j#`1r-)z zV>vzl1J#~1AawE%2eOhgzbiS;$H`K<#`a~-oJbx(3WX20@prZ>zzj)=&T_ezS;n?m zlO`F9)_wfZSFo;^h4G9EGy+Re^Q*>n1XI#Pr#3hB^QitBN9a^7>X}EqOV?`94;FdY z2lly$W%knKV95X+c{?eTsp`T#I7|6`k|6eWoHM9~xKkzqIAV8L!kwb-cL@6;tDt=C za!8mm;(lRTzYODM|9u7(Hi{qMrDNm*a^?q48~Os7jE6kjRjG8zhsz5#4rPcS#*cvw zlvirvm~Xxhr6SHK8?}@xJ5%FsRxlx^@tg?-%pSW}3$eFYRyR=!@?D$zKTB$*XC0LE zK#waz-DpQF@n3q3HND$YxC9qVPx^Q7_wnyy{l+{{*faK0c2m8>?+GJ+0CsIUp&pOH)x0euFd=0^;r!)|~WIMW5< zM5Xkx_z4hUA7`A6yZ2GwbmMEOck@$y0`b%%W3K;%n50q6Gy{JGb!WX>b4oCkpV6x< zIQf^$Ud`8~BrM65)&rdJy<7E^2*b8%V3igwxiaLEWVqj&@NGWE6|-r700NeyfFj5Cwxbzr z4*BVcB3PMjuN?&2QCRzTioWG&p-28mX}9-M;4zfba~K@WY8jQe%L9_nXKeE6ol8io z8>+J>#^2p*1FQIHeze_h0|7ca1wmsOY+E_>8jF6fHLxM;&Z!vku}lTC+4x2@z{zdZ zd%j6HvAF7huFpIehV8G_y17B^gSKw);5=Mx zIsN7K9=Y*m&5d{*8bPXlL_2gKZ&A-`OVlW+lXW_-yHHt;w$QjNv3G6)BHaurf)N>} zx9SiO8S1{5)=gVjt2LE4UddUdYQ3Qel&$+=4DngdtptO?Id7W({-#Z50V`u3y(j<{ zHJ=&)nOq+rj{%REhkKi=)}<#EidJWX&TSSP={B6`VE(=BC4LtVcB5H{E{+{j*wx^hGrh5=Pv+JV+Pa z@i&`|fkKW&4R3n~Ih$%IU98U(?0GKS0%Bf15cBe(^Dq|eGMJW0@n+^-$y1TvNsz3g vfE+_Zm^4YFDM~nTXwsxfQ_+oYG|BFXhK;*I`2_xE(z>;tA7`%#Jox_rEA$!X literal 0 HcmV?d00001 diff --git a/styles/globals.css b/styles/globals.css index 92e55eb..f5f0aab 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -93,7 +93,9 @@ body { } .chessboard { - -webkit-print-color-adjust: exact; + background: url(/pieces/background.png?2) top left repeat; + + /* -webkit-print-color-adjust: exact; print-color-adjust: exact; color-adjust: exact; @@ -103,7 +105,7 @@ body { white 2px, var(--color-gray-400) 2px, var(--color-gray-400) 4px - ); + ); */ } diff --git a/types.ts b/types.ts index c310197..4ae35b0 100644 --- a/types.ts +++ b/types.ts @@ -5,13 +5,17 @@ export type ChessPage = { games: [Game], } | { type: 'three', - games: [Game, Game, Game | undefined], -} | { - type: 'four', - games: [Game, Game, Game, Game], + games: [Game] + | [Game, Game] + | [Game, Game, Game], } | { type: 'six', - games: [Game, Game, Game, Game, Game | undefined, Game | undefined], + games: [Game] + | [Game, Game] + | [Game, Game, Game] + | [Game, Game, Game, Game] + | [Game, Game, Game, Game, Game] + | [Game, Game, Game, Game, Game, Game], } export type User = { diff --git a/utils/convertGamesToPages.ts b/utils/convertGamesToPages.ts index 1e1f47d..1d41931 100644 --- a/utils/convertGamesToPages.ts +++ b/utils/convertGamesToPages.ts @@ -1,155 +1,168 @@ import type { ChessPage, Game, Settings } from "../types"; -/* ---------- helpers ------------------------------------------------------ */ - /** Strength score used for the primary sort. */ function scoreGame(g: Game): number { - const w = g.white.rating ?? 0; - const b = g.black.rating ?? 0; - const wp = g.white.ratingProvisional ? 1 : 0; - const bp = g.black.ratingProvisional ? 1 : 0; - return (w + b) * 100 - wp - bp; + const provisionalPenalty = 20; + return (g.white.rating + g.black.rating) + - (g.white.ratingProvisional ? provisionalPenalty : 0) + - (g.black.ratingProvisional ? provisionalPenalty : 0); } -/** Total half-moves (ply) for a game. */ -function ply(g: Game): number { - return g.movesCount; -} +function validateGamesFor6Page(games: Game[]): boolean { + const sortedByMoveCountGames = [...games].sort((a, b) => { + return b.movesCount - a.movesCount; + }); -/** Total ply for an array of games. */ -function totalPly(games: Game[]): number { - return games.reduce((s, g) => s + ply(g), 0); -} - -/** Max ply in an array of games. */ -function maxPly(games: Game[]): number { - return games.length ? Math.max(...games.map(ply)) : 0; -} + const limit = 85 * 2; -/** Median ply of an array (used for similarity). */ -function medianPly(games: Game[]): number { - if (!games.length) return 0; - const sorted = games.map(ply).sort((a, b) => a - b); - const mid = Math.floor(sorted.length / 2); - return sorted.length % 2 - ? sorted[mid] - : (sorted[mid - 1] + sorted[mid]) / 2; + return ((sortedByMoveCountGames[0]?.movesCount ?? 0) + (sortedByMoveCountGames[5]?.movesCount ?? -42)) < limit + && ((sortedByMoveCountGames[1]?.movesCount ?? 0) + (sortedByMoveCountGames[4]?.movesCount ?? -42)) < limit + && ((sortedByMoveCountGames[2]?.movesCount ?? 0) + (sortedByMoveCountGames[3]?.movesCount ?? -42)) < limit } -/** Distance between two games for clustering (percentile based). */ -function distance(a: Game, b: Game): number { - const longer = Math.max(ply(a), ply(b)); - const shorter = Math.min(ply(a), ply(b)); - return (longer - shorter) / longer; // 0..1 -} - -/** Cluster games into groups of “similar” length. */ -function clusterByLength(games: Game[]): Game[][] { - const CLUSTERS = 8; // tweak if you want more/fewer buckets - const sorted = [...games].sort((a, b) => ply(a) - ply(b)); - const step = Math.ceil(sorted.length / CLUSTERS); - const out: Game[][] = []; - for (let i = 0; i < sorted.length; i += step) { - out.push(sorted.slice(i, i + step)); +function validateGamesFor3Page(games: (Game | undefined)[]): boolean { + const realGames = games.filter((game) => game?.id !== undefined); + if (realGames.length === 3) { + return realGames.every(g => (g?.movesCount ?? 0) <= 50 * 2); + } + if (realGames.length === 2) { + return realGames.every(g => (g?.movesCount ?? 0) <= 80 * 2); + } + if (realGames.length === 1) { + return (realGames[0]?.movesCount ?? 0) < 200 * 2; } - return out; -} - -/* ---------- page builders ------------------------------------------------ */ - -/** Try to build a 3-page (3 games) that respects the 165-move limit. */ -function tryThree(pool: Game[]): Game[] | null { - if (pool.length < 3) return null; - // prefer candidates whose median is close to the pool median - const poolMedian = medianPly(pool); - const candidates = pool - .map((_, i) => pool.slice(i, i + 3)) - .filter((g) => g.length === 3 && totalPly(g) <= 330) - .sort( - (a, b) => - Math.abs(medianPly(a) - poolMedian) - - Math.abs(medianPly(b) - poolMedian) - ); - return candidates.length ? candidates[0] : null; -} - -/** Try to build a 6-page (6 games) that respects the 100-move row limit. */ -function trySix(pool: Game[]): Game[] | null { - if (pool.length < 6) return null; - // brute-force all 6-length slices (cheap for small arrays) - const candidates = pool - .map((_, i) => pool.slice(i, i + 6)) - .filter((g) => g.length === 6) - .filter((g) => { - const top = maxPly(g.slice(0, 3)); - const bot = maxPly(g.slice(3, 6)); - return top + bot <= 200; - }) - .sort((a, b) => totalPly(a) - totalPly(b)); // prefer tighter packing - return candidates.length ? candidates[0] : null; -} -/** Try to build a 6-page with only 3 games (template allows empty slots). */ -function trySixWithThree(pool: Game[]): Game[] | null { - if (pool.length < 3) return null; - return pool.slice(0, 3); + return false; } /* ---------- main export -------------------------------------------------- */ - export default function convertGamesToPages( _settings: Settings, games: Game[] ): ChessPage[] { if (!games.length) return []; - /* 1. Strength order ---------------------------------------------------- */ - const strength = [...games].sort((a, b) => scoreGame(b) - scoreGame(a)); + // Refactor the pool to be sorted by strength + const pool = [...games].sort((a, b) => scoreGame(b) - scoreGame(a)); - /* 2. Best game always gets a full page --------------------------------- */ + // Build up an array of our page. const pages: ChessPage[] = []; - const best = strength.shift()!; - pages.push({ type: "one", games: [best] }); - /* 3. Cluster remaining games by similar length ------------------------- */ - const clusters = clusterByLength(strength); // array of Game[] + // Greedy page building + let batch: Game[] = []; + let current: Game | undefined = undefined; - /* 4. Greedy page building inside each cluster -------------------------- */ - for (const cluster of clusters) { - let pool = [...cluster].sort((a, b) => scoreGame(b) - scoreGame(a)); // still strength order inside cluster + // Add games to pages until we run out of games. + while (current = pool.shift()) { + batch.push(current); - while (pool.length) { - let used: Game[] | null = null; + // First page is always a single game to highlight the best one + if (pages.length === 0) { + pages.push({ type: "one", games: [batch[0]] }); + batch.length = 0; + continue; + } - /* 6-page (6 games) */ - if ((used = trySix(pool))) { - pages.push({ type: "six", games: used as [Game, Game, Game, Game, Game, Game] }); - pool = pool.slice(used.length); - continue; - } + // If we're in the first 1% of games or first 10 games, try make a 3-game page + const inTopPercent = batch.length < 30 + && (pool.length < games.length * 0.01 || games.length - pool.length < 10); - /* 3-page (3 games) */ - if ((used = tryThree(pool))) { - pages.push({ type: "three", games: used as [Game, Game, Game] }); - pool = pool.slice(used.length); - continue; + // Try make a 3 page with 3 games if inTopPercent + if (inTopPercent && batch.length === 3 && validateGamesFor3Page(batch)) { + pages.push({ type: "three", games: [...batch] as any }); + batch.length = 0; + continue; + } + + // If batch has less than 6 games, keep adding unless there are no more games, + if (batch.length !== 6 && pool.length > 0) { + continue; + } + + // Try make a 6 page with 6 games + if (validateGamesFor6Page(batch)) { + pages.push({ type: "six", games: [...batch] as any }); + batch.length = 0; + continue; + } + + // Try make a 3 page with 3 games + // Try every combination of 3 games in the batch + let foundThreeForThree = false; + for (let i = 0; i < batch.length - 2 && !foundThreeForThree; i++) { + for (let j = i + 1; j < batch.length - 1 && !foundThreeForThree; j++) { + for (let k = j + 1; k < batch.length && !foundThreeForThree; k++) { + const trio = [batch[i], batch[j], batch[k]]; + if (validateGamesFor3Page(trio)) { + pages.push({ type: "three", games: trio as any }); + // Remove the used games from batch + batch = batch.filter((_, idx) => idx !== i && idx !== j && idx !== k); + foundThreeForThree = true; + } + } } + } + if (foundThreeForThree) { + continue; + } - /* 6-page with only 3 games */ - if ((used = trySixWithThree(pool))) { - pages.push({ type: "six", games: used as [Game, Game, Game, Game, Game, Game] }); - pool = pool.slice(used.length); - continue; + // Try make a 6 page with 3 games + // Try every combination of 3 games in the batch + let foundThreeForSix = false; + for (let i = 0; i < batch.length - 2 && !foundThreeForSix; i++) { + for (let j = i + 1; j < batch.length - 1 && !foundThreeForSix; j++) { + for (let k = j + 1; k < batch.length && !foundThreeForSix; k++) { + const trio = [batch[i], batch[j], batch[k]]; + if (validateGamesFor6Page(trio)) { + pages.push({ type: "six", games: trio as any }); + // Remove the used games from batch + batch = batch.filter((_, idx) => idx !== i && idx !== j && idx !== k); + foundThreeForSix = true; + } + } } + } + if (foundThreeForSix) { + continue; + } - /* Nothing fitted – pad with single pages */ - pages.push({ type: "one", games: [pool.shift()!] }); + // Try make a 3 page with any pair of games + let foundPairForThree = false; + for (let i = 0; i < batch.length - 1 && !foundPairForThree; i++) { + for (let j = i + 1; j < batch.length && !foundPairForThree; j++) { + const pair = [batch[i], batch[j]]; + if (validateGamesFor3Page(pair)) { + pages.push({ type: "three", games: pair as any }); + // Remove the used games from batch + batch = batch.filter((_, idx) => idx !== i && idx !== j); + foundPairForThree = true; + } + } } + if (foundPairForThree) { + continue; + } + + // Make a 1 page with the game with the most moves + const maxMovesIdx = batch.reduce( + (maxIdx, g, idx, arr) => + g.moves.length > arr[maxIdx].moves.length ? idx : maxIdx, + 0 + ); + pages.push({ type: "one", games: [batch.splice(maxMovesIdx, 1)[0]] }); + } + + const stats: Record = {}; + for (const page of pages) { + const key = page.type; + stats[key] = (stats[key] ?? 0) + 1; } - /* 5. Done -------------------------------------------------------------- */ - console.log({ pageCount: pages.length }); - pages.length = Math.min(40, pages.length); // limit to 40 pages + console.log(stats); + + // All done. + // console.log({ pageCount: pages.length }); + // pages.length = Math.min(5, pages.length); // limit to 40 pages return pages; } From a4a28b0e4e873cca476b6fc89a6e12ab3543f399 Mon Sep 17 00:00:00 2001 From: Paul Hutchinson Date: Sun, 14 Dec 2025 15:25:12 +0000 Subject: [PATCH 2/2] An attempt at some optimisations --- components/Book/index.tsx | 54 ++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/components/Book/index.tsx b/components/Book/index.tsx index 85bfec4..aec5b60 100644 --- a/components/Book/index.tsx +++ b/components/Book/index.tsx @@ -251,34 +251,36 @@ export default function Book( ); // Spine - pages.push( - pages.length ? "turned" : ""} - onClick={pageClickHandler} - pageNumber={-pages.length} - > -
+ // pages.push( + // pages.length ? "turned" : ""} + // onClick={pageClickHandler} + // pageNumber={-pages.length} + // > + //
+ //
-
+ //
-

-
The {props.data.user.name} Chess Book
-

-
- ♞ -
- , - ); + //

+ //
The {props.data.user.name} Chess Book
+ //

+ //
+ // ♞ + //
+ //
+ // , + // ); console.log({ allPages: pages.length });