From 4b383664bb43b302d5225770b1dd8cd1745dbe67 Mon Sep 17 00:00:00 2001 From: Steven McLeod Date: Fri, 3 May 2019 21:29:26 -0700 Subject: [PATCH] Initial commit --- FreeCell | Bin 0 -> 12416 bytes FreeCell. | Bin 0 -> 104832 bytes FreeCell..rsrc | Bin 0 -> 2432 bytes bitset.h | 26 ++++ common.h | 35 +++++ freecell.c | 265 +++++++++++++++++++++++++++++++++++++ freecell.h | 63 +++++++++ gameintf.c | 245 +++++++++++++++++++++++++++++++++++ gameintf.h | 26 ++++ gamemenu.c | 131 +++++++++++++++++++ gamemenu.h | 9 ++ gamestate.h | 13 ++ gametypes.h | 31 +++++ gamewind.c | 344 +++++++++++++++++++++++++++++++++++++++++++++++++ gamewind.h | 33 +++++ gamewindlow.c | 156 ++++++++++++++++++++++ gamewindlow.h | 77 +++++++++++ main.c | 144 +++++++++++++++++++++ pstring.c | 22 ++++ pstring.h | 6 + strntol.c | 90 +++++++++++++ strntol.h | 8 ++ 22 files changed, 1724 insertions(+) create mode 100644 FreeCell create mode 100644 FreeCell. create mode 100644 FreeCell..rsrc create mode 100644 bitset.h create mode 100644 common.h create mode 100644 freecell.c create mode 100644 freecell.h create mode 100644 gameintf.c create mode 100644 gameintf.h create mode 100644 gamemenu.c create mode 100644 gamemenu.h create mode 100644 gamestate.h create mode 100644 gametypes.h create mode 100644 gamewind.c create mode 100644 gamewind.h create mode 100644 gamewindlow.c create mode 100644 gamewindlow.h create mode 100644 main.c create mode 100644 pstring.c create mode 100644 pstring.h create mode 100644 strntol.c create mode 100644 strntol.h diff --git a/FreeCell b/FreeCell new file mode 100644 index 0000000000000000000000000000000000000000..6d2f640b7bdfca1f22578f745c5362b105a559c1 GIT binary patch literal 12416 zcmch7e{@sVk^ekdNJ2b};=05Mj`eUTaln>@YlxE?OO}lhsv{g*{0L2B8CwztTT+cp zY1T_*lGYB8n6f!(w&^BroBbl~X(>yxJ*hX})TC*?v?r%ulkN7f*_<>DTSD2?aY#xX z*XZqM?t2m@zuMhD_UT-``*UXQ+_`gS=037C_J+gt;qGqwpFMTW%}s7RONqwu%RSDK zGZz~}Nd390t22XxpIgZ_5>mx)h{}mit=?qeaRy&p<`H{Mj z9zuE==@nY-1bxmGNI|6iNZ&(x8EKkUyGoEeND-vNNMlIiq1?!1TJa+l5AtrRkg|_~pKRgE+i1D^vE?4UJ|rH`M?6}GQSl@{;^B5ImU>ye zOnLb6BO|>*CNj`IGKv~1Jx9%C3~W}XeQ#1xS&U>wjv5s?WUBl9oi)X%5cI@EnhK1W zCcNM99%U%Lp3CPxOhs9nB5xguDo#1I8@*mbk52>ln7M^ZsR2+9`qM>k+qZ^!<6}*`eym?RXW(pWNO$Wy2J?%K;cBnH*)4VF_C5}VsPy2dkKiVhL zUzcNKP$whCeQGFzyoCH4P8nG9xE<*N(p36Ep1FDf@G=!`>Ny4Vqc@|MRn&{J(-s)J zuGB%IZAC&lKD6;|%8yy(A)>#gxCOnX^r_JOnE!n)bJMSFNuK+mhVFQDV4pI;L}92jK{m5;`{{8@85uJd zBl+lMiRxIrL9c@fHJnNt=#m2yha0-0U40v3iT=LEuI|2YueQ#UPFZk$a4y%Q+di4! zE{FP_Z+vi;3~N2e<+yKL^D*2%z}!uLoq3_j+yl&qy6y4)KC$|Qvy7}5JDV=Awae?l2dm;xXOABvo8Hf)@xx&c+?}22K>;^ANyrwUuN@$(IxGG& z`zTa^)t!ax7D9e1)y&qBmi2&c44+>+ARSca^jLJ!_<2cX_yujo4>B#y-R)nAbcMS+=ir&k*V`DTh9$9R+!ig$ z;?e5t*fR@Vca4Os8vpYj-a4YKfX@GM4c9@&)!G;DZI5c&Va^Kb>*G!29bVr{mGm;t zsZL-k7TB(;XN>Z5b1;@}9nn@)$M}qf@gc_881IikD|N+V+KNuQ9YXq`SMq}u&@bTG zqHzaV?iz^-4%(3i{JXEfU-9uCP7Eq3OZmlDOt8k*>Ru36nPYT1c)t-iElQusmXtUd6(?D|(e$L!*1Rz=!Tb5njn10py5Nm0 zE9ucv(688|#k|X0MU{|`sO^MjyWCnAH8%S~)1_xh53{Cf9tlj0cKCWSHPHb3BihZ;))g0(YuNMMi2E!6){>&N3LO<4g5>!;DpZ4AaycVqe zWIExuM|+`}CuI*=w(qE)p}iBHm*2roJGPK zIm^&mDOxW_t-Q+leZi5CV#hg#{0`&`vCdQJQjMNJRXL8KbR+ZdpszjF*&SZD2dtz5 zby7Wo^f=O_`U=vstdsOp4%~B=U(x9cshr(do6&BYmJZ2%aqyC&Cj^ix^`Md3@buO?^op1oD&);z1UlkIg>rBN3>bp9`D~BZis(5M)~SFb-%g;v+G6fCEj_kLSEsM zBvXa76+7c;aEC3fA~GgsVb`^|NxYlcs?#|4hnWhGI_=$}l|KC{?-X~9ssch;aKsM^ zB2&(a%qg4*FEMxT?`iMSG!pj7UMd6!vF5Pne!;DW*yiql3zR#Fh_oH-4c-cABm+2M zZ-*1q=MKFwIcd{6n_G_$Q@-AA>c z0rpvSLeZ>3SZWl4X)-{elEk8Km*6BY{dV}~VGC(F|I-$V6208gC^wQN{3>J;Gt8SF zAgtpya4fI>veOLv*K11B^2dK-Ns`?`iK@emtW}(nV(zuQkrEbiOYdioc-Gdb89#I85WAFv8zfwPHcbQrn{hr8MY~v;fvhzCgZzRXR z9sQArX5(UA%>HB02&HU+0LGjXdDO0-w&z)MkJ`0U>2I=78oG8T;*i2{yRZdVU*-|7 zsU``=#uBPMM)`WFx|ncInfVOYR)t=$IVd08AKOLr!z~oOd#9;|cGiR}n`kG|lcBBX zZ;F<|rZhn#Eb65Kv!RX_wR%o?BKdZ+-4qxzHu$kp5hGT)FCm2zJR*|MRw$1EzZOQh zZ7bS>+_q)BfkJ-KOV0Lc^+YQ_h@*vZ6zR3D^Bx_y>wMh_5)e&*XkgCL_*IhG6pprc z-;Se%ZIP_ZgD3fdFOcTU8Q%AT;*z=b`$h7J28p9&IER_Y;Z+FoKiM+430YbL9^ zz(pBcJOVC01TL=FX#p3B_J>U1<2@l4X5pZPu(Q60lfuIuf}y;G<@;`}rKHMY(Q;no zLcUz>91cY|4=Cs%hn6ER_}l7%rJVt9(9$>cLRFD`KWU}w_7}-wb-lYhplP{S*)@V9 zS7@2tETApYXu|FplT5HI7HhTF4S@=4E-z;_UvBpc>ov<`HKXYje>tr0QaApQ3fR-j zyxJOE%N|0vP&gf8U_)GoaSO>OH2Ubr= z4EB&_a|Gp9S{N?F*|H!o_Q#CFJ|Ml?%6mMRET`wb)}8M(a|=CLmR#;>9zn~eu02Mw z#n93UD?&)+gLR#3(c}y%mPp>o{$?S6!g~Xy!V0so6$ZXMxITQ9@x^1ZlI9PJafPix zU@L^&UN_D}fW0lVUWP^xI)Yb8QsD9H`u*^_y$PJz+St~v zdo-_~Sn}j((H3phTPbRrTi!&GWhk?55;75GTzUyI z&*PUvOR!I@*8;+vKAW^YhJ;iuytgBD>;T7L>c^sBLA<|rH)H~~9d>PLoGhhIl0!z@ zzqJlVW&-_J*GCsWb!xkO`sBZyyk@D^p6!<)wh(jU4XuMw>r<6Gf3}#nOsa4NI=oXgk@upRq&#i1-GivxZ}6up;1ZvyMZurczu& zi<#!7PUu`E`UhA!*QJK=KRGbdus@M9pYvqSUx5Ov z*JaMZca%xK3*P&47!iKe z0c%mnoaTMC^H%3wm5|3LDW53>6ssMQhtVnSsrzZ@36CV}r=5dUcR}UdJJ4R$J|GE zSS>FD)pB=|$Z*$4#3l!mgq1{$$$K<)6gt8^5oA9sQ859Zj{PvrGdz@~RQJPjuvyC4 zzhT&(PlUY!QUiMjbC954RQ9yTXGxw2VkYj7!g)piYDgI;&IAiq#0ncXzXmJgUmr2- zlb&nrXu3j2=9#PLK*TOd(Q?Sxi>HHE=EdjI@y3WXJ7dfpv@jhZ#6&fI;B*qF#}P66 zoDk-Cz!oD@0~wGyL5J7_fa@l=ndt#fnzAy_FxZ`k!Op_0!^mnRZ_+zxvlU$TFGZ)v z{gTjaQe=j8PM}|p04CGpWo?Mi)ueCH;;`urTz1wpv}|rk-;Q;HF48Pd2XGtKV_(@` z|G|Lz5;*NL?{XPEG#D*{{o&bZ36*a0>?{ctx?wre8TgI`oRg}MvxwZ2!I7**_rUGp zSij~w>gSihzn}5@fhk(5MQGEvinmdJ&(q(g^sOPpx}odx(zoe7wD&x{Y)apzN!fz} zQ)d@5sV`?YegGS!6|f{g#X5=H+|g{q@A5b@ip04d4*5J1gBGdfP;Am z|Fn_4H~6+c#Q$_^H%ALc?cfSxeiwQ*Di1cq>k^6XFcT|w*^CyUO>aOfdd9^ZY1!3$ zrzhI9(-QQ6tiU`B8_y9KJD+0B(Bgr$=71HewRNMdAlH*)S4Q@F zd4C^hh$C#OBNl5Ru$Aso0KZrp|A*uJwpUVz)gAvb2hcx;u2=P5iQX>6Lq4vTCEKe%#-XQ=fo0Nr z?M}3J=o0%8bM)xF`V)yypv%R*hP!s})miZYoPMm)PAjjt)k8|i$T;3pvJj4|!pA?4g#SqGI3utwWfPk!2)d8V3bYqkm}oIsZ_U zq&0E_RYdYxqpKOMZ^(+el@TMC{EfI9X@sS)EMYi4w7mo^u1JwwNPAIVNLwRvQ8`Jz z`-6uz4Gq}*jkqJ>9>#3ikL}q4OjZTXeXTa{67A4#$ zAu{3TR#3J#OR;wM5;d6`)QQlFY%R+=?svzd&Dk2AQ^v$Cz`W&A&$A8jDt~=X!Pqf% zX1mP)5$iLs%i_j8YN%k3Byi5-SjY-;HM}z1DmWFo-(TNd^ifo=ppZq>8yddq=ev-S{?hD)oKD-7Mal(!2fk$Q%0GG++o3T|)bL*r+pyaSpqQtySb?*pVsN z5oeaSrTCWdyl(d%cP>c%yNCp~Rp90YbJ&4`HCAL z@DSEidRh9~vNmA4$mfY@IgbBadUHY)R&WZ_;05jA3&vqC#w0KZjel%`Y~X+-4`N zxeE<`L}w6v$La>gUPrv1p^5Ttz^aO}2{G#RA%n||8WlJER#>=~ zZiXC?am_I`gB|(29-n*?@QEH}h@fA1WLO;$7bv~MeT~4{a5YxUsPk%=pEUXp)(?$d ze1kXv0HfdvT_L{tHK>^z)~l+Zu|%hk)J`vS6MEo4v7?qc+I zIQ6KX8xgvTDM(>)wq~nG)IE&4+es44|N9)r{BAC#96a#UCTz?K8-Hh!lj0~U>%o~ElP9g-uaegEus9%GZAa$^&x}z zx;uWR&i?byB9rOwroStCx2$W@dXIiaX@0}AY+=Y$S@f^KaK|Z}4&Ox_q^+{3s_6LE zEkDCq>y2gtQ_8Gf_voCu8NJRMvmUPNF->^trf|z=V9kbLV-{q&a&KCjn1esW?z{E(VbyB2}Ddekbut^XF<4}C)WfFk{w9_4?g zUA_XrpWIpOAKWQ7BpU{STfu*SLlU{gqNX2hR?%WYZNEd@6P`$Rm`b531O2dqWM1(~ z@_KxpK>vxM?bka*o@;%GAo$ilbkb$^t*-`I$XM!Ji((IZB~#?R|zE6NQb)&x`8~x*O?o&TeYojlLTmL+~U~{;4H_l0AmBXcV zS3s&ux_0vuGJ*GBMcz2yj|8B%RmEex9*sl83q7&jU)GRF@UYS-ZU8`;_S2Pk7jRuE; zZ%~xpuIP@w-AZqGceoe#_pCn1%-=9-Xat{&c^t}8W6QcmQJ>sUAGlG=p_=GkEMihd zG9Zu70u|J$@1SKqNy-M262C+GPvo1DdGAhpOa*e%TCjkuzyBSX)Zanj|98>_eA*{d zo;ocE5PytcokhxM_}!pERpDTKC?)e$b=<6~$wg@6ggjvVcTxuZXRpRjh`;4wCk@hm z&{HAylpqYw$DgFaYs9xp3g1KVXRQwSO?adV(FK1kniO*p1Ku{WyKaFkAjFXZ_@5*h2` ziLX#S(Y$5^o5F-MXP*A$kp>I@(l-xdVX2N52tF^u8hjg@2P;n24x>R1C4cko+pqul z@yz@0R>jBGA}+|_il-+{kY3KV((s3QoU4tV$owKM$ZZ`{?3;#dYomzVDz3PR3_(5Ay$IpJeujp;m*+t#&cm6;v8voMZ*;POK zlc=k8Kk?#Ex3s;Vy!YhURo~L!)!MCJ^?v8`OQ;?F4rz78`3A$Fr@lmP|3}n)Hojy{ z^-_z`EK%~T{f{S~sCB*n=8xWa&HbC)WdA3b`S6$uXbH}w3R;8TT56!rV{PxmMvYR8 z`td>P*XTidIQM&uCG_e?S|BY@RsPp zUSl89{BFg&)NHRE_}sg{{&j5=*5>t0;( zNk2NmlWl8NRr8-gP-M{Mi zi%V)EtG@Py$M)<{Y|86{=2hyz+BaXc*Sz)4bMDufy1(EtEFKNnB zGI#?G7$@axs#3cCc5fZ;)8)6X3v42TRN1&O5X2i>O;yVPkjn2a%0s{YzB- z#b==*)oP*%)Vp~0VA2>5(fWMmEiH$2C)_QUe{q7D6$O4D#bF!lB^&i6B(q@4?-4$ED4OM10$r-4+v=@ zBrH?v$b{O25Svifq11I`Ol)u+mk`&bT-TK;b%?L)65_hl^|iTN*Wq$qLKN2}3Ptx_ zd#^on<}ixp>yxLw&-Zl0(mH4F_g!oMpM8Eucx(E?;mUA#cfp^Fe@|#=Tv2y-MQy!7 z-? z^Tjsu5(|_qxrZ$NYG3~msY0Y&MsCs|>5xpwOh`5)7cvJj7cvi04yk|yAj=^4LbgJF z2l5rj5aeOVS0OtgyCL6zQ20H_laPIoZ$n;$P<#J3$g24^7cO={|oX@h~EL(kL`=le*k$I+Z>FSu<;AT zk#7X!QHTfPgWSMz7(>SAAU8q23wa9idB`b9HRO55EmxZU8cmk%Rk3aBma!T*T^F2S ztU=uUkS{^jLe|9?Mo4WNZHV6p`D5%mAlXhsrR0_0@Xp^4mW!eJp-;v9btt#+LR>6D z%=nG2p4RSxwy;^Yyrs1^+|m~A+q{^_E|F5a=eGB;HADDCYPu=Jc#+^5Rr`(+q^7_x z%`rI;9WDvszJ~a7F!LEjrtcJQlg>JBJ}C&DzU)oIAyBWvDa4dPO0xyTQ*aCkaZ@H{ z7G?jEd(1eOGKW&YC*Rw#8G>AZaJb=+5c%k{j?+S<9u&f#2~T-4G1pOKR2hxkW^)(# zs0(8!;yZzrHkE-JPGU-gIJ4BgKTFiAE%A^$B$|<@+p}H--wpobNI4SEpAitE81_8n zy8I%a`89H$6dhs>-cJNcd zI|zYwYyM{<-wUtA`S(b+5E*jL7Y`tJ;3F7^Aw@Pm>go%*@P97q-y|$TsbiJEXjxJw z4DeyW_%lcYWQGFJ-2L{*7KBXjo#4@2aV*|{_ADX3h*G+c zFXl({Zw!lOAzE5Dc7Tt5Y+%kXM!>iD&z>*DUD@F7i1Le2atbx6SBzJ|SP5~^anUg+ ztvS58->m2hw+P|W%N6r>^EV{n|M!2qaz>EF97m8FOT_Jl4h{->C#IdA_~4HQjt|LD zo**c|una>90;O|z`OmBU@qZitH_R`XVN{^Gxd`K}s&Q4LZ8o&ej{n*FGi>=&1(>B` zv2zULJzBq)TjUXApG?JkhOk4tC!RD+r?aVLOIXG{jhG{daaFeTw1&Gev*>a1*E>Cg zGFvt;lE|CH8{!V5U`XzWQt;})ORe4zZtYibjfiWjXlSTkf`Cyt^o4C0rB`vpP%~h3 zq$ixIN-<(FJLxbJGsG8$G1FisVZO;}rH+T_xNV!M(29nY-zmf*%r-8}uFU_{Gu24E zf0}8^=74`F%8%wh(M&bvf{l+_yX$1M{Le+3g$ogzsf=zOn>6PS%Iy>5SLtMip*~Ml zU&{lPEq!gKX#(?K{ngT6QAay*1lT8j4gD3i(8hW#l(rcAmSZ>e;)5eF=&k#sw}Zz; zGo)>P*|K2O>fmwrqNQWwZ_eF~zo+KzKuUM;c*+69k7D}+$P0mvTC{U*uW;7hTRVtu zyJ-Aq@OWx4_@~*z+ywld!=K)B#k)2lg2$b2bhzsFbU22W1qaMh?_hhz{IWGGg4My} zt}~`Dq8A%;3Sot_g;5huP;M(9oLEq-SRe$aiJ)sGKReOJznTJ{2g=laCwm%HT7;s_tLRzPtVNV!ewG%{fD#1n;)+$HE!>493Br2M5Z48;p)dtw1uxB_>1<8A`^LN?6@#I z54@p*ZwCiZf7lioRBv)G7cH^K?Qh3Y&Kz_+7xA6mbL5!gh4ws@`;+0{G#m12^Rh$U ziVMQiZ|ulEed3-Uj(@sm=jpGtJAwmwVz_{CrZGGxuVKYc$4AT+fkPtmjxMa3W(?mD zc)qqv_{biut-E0nQ@HYn92P3VAe+Yer@IX9q=CH`%?H6g= z7Z|_Vv3^r0FiwiE{FiLUYxlIjVZn^#xlZM}No%s2PeS$ub% z`G)e&d0e3P;VYl)>wT@+{$*+ZB5bFOUoF1*ezLv$%s0=VBzx;hD#ouZcs2NClomB) zoU8tqZ1Hzz7i4!7qn74$WHeT^XDNTig-|wc9S>#j)`^e{tn=`=7p*}S-lZIyTd&SA z7hRoEcL=>Uzc^pycjV2<7uoj(D*svde|$kW|L(rxt4qoPpUb=;?kc`|`0_m!?Vq5H z%Vqy7Z-0Yq{PK*s=$B{u#`}e|lk-}>xT`&DA*|K@Rfq4LuO2-loMC5PzHnNY?|}D` z*%0yX9s2ldMX!InO9X~Q(FxdV=o7`+McIz>uZ;iXYp5G@!L(3j-mSv9cW3eIl$+uc zGAF0TKM5@~{lhEGay!G<75s&8bYw383w1E9HsTk=3jQMR!+TDRUp5yBuUX#iXmJHk zV;qU1vf;mYwkFS0RCegw&(|E?+2O)?pC2-PrNU_xhG{P^l!-kD?`4Oah?`Y8Hhv;+ zXK-NnFMkrMoEfnJX>URJrHs(OhyJZ!FOz_>R(!GZj(V=v^@|K2};g%Kl6l)dx+yadykK8moeQO<3DqJtXsyc;TWguVHCno zy?OjVw7;Xp$B%`a`1>yPI@IjC;_L<4M^@ka!|}1YK*zP?X$#vVa=W#K6D6J7|jP&f-Kgfp*Dcna5HF8@gx7B+K@+Q7-b$`|vD+Uiq({Z3VFAP^XN z{q*+w4@FttYZ}3$ORXq&fCjJgo z4Pk4Bj2S?hSEjfSJ5W%DW8(PHp;lL(z!7QS$fN6hL$BAC7mGl7ZFz9u=%ed%3gkA$ z)-GKvYL`CtdXc+!X|X#vKs9oH=lQPzb!F)Fkw=SDPK*?!)GmE{q;~0!{!=n?agm8?;gH2VxUEw&&)DC zh!5n~MllP`yUmnPcAS^I#-(PtiQ4$ewL%YO`TBnt87?^d>^*-o{@x#)oLet#-Cb9W zqt0(&9H6a>%MM?z9Q%bIHG2fxqu72G+Xu1zGi>j|_C9R?9NPyi$4tMOFJ^Rj@i(_4 zD>x?lfr|k5DRBMRZ^M3w_xrHljNJ614|Za`@4vQM80}zOh7Lgg40;hX ztw@TYe-1ql8pjclXRZql6dBLd%&Cp|o|#o-3@c|P?w9wKkaau<$*o2^#37#lyCjF)FBp}%ohvZBI$(e5+!qmD9IlcCAXT8 z8c09n0OT}eOq9%<13~<}Zpf365y&M`a$6Q;5hMhGo!ef9d>~5Z`yj=T)sTlE2O)1l zE{l@eXF$pz>mfTKMF1N{VwKiy>{0T@d&wh96wWEcrbAe7+vC z1+owF8svf~S&#u)09gYWg1i8E2ZDT;W}&^`gg2ev^FA3%8mAB&PoAEX#^Kjaa}i;y#rE25+- z4}yBD>VfQoAYWAH`q?sfN88l&5AMqyf?gIRrucQsirC4WtdS z3vvVkKTAIqCABjkWsvodosbtGZ$XgnWm%Agkkt^>&ob1{GUQ{~1yNF$0a*Y+KI(=b z$Vc5N$VE|dH_CN)0i+oM-reBc{RZR{QBn_HJ$UtKr+V<};jbR`S3fFBmc!q2_*;(p zSpFE~800-s5)_cRka|cD1oaU_K7z={3fNl#dn;gX#SRGUtw8=)z+MCFH7tgp+zpRF zUWB0B4Oc|TJvk7R`yQ109+dkY@a_Tc9`G8oA&Vd($acsf$lH)lMM={vNEM_V0$$Tg zkar+sqNLe`)Iqu-`ysDFJ`g1tSc!bBd{a3poaX|26Qx2L9Kq zhM+ua;Aai|tbw23%7K(aHbC}3UV*$XO73?-iXf{XgOH~oCm|n;k}vrn3n3`amtgNp zu=k}mAfJemwV4p)doA+47WrO_e6K~m*P?#bA#hAE9>0 zZV1X1Lb*aHSIZno5Yi9X4;g_VAM25i^~lG1JEvUj5l-Kfj%Q;^R@$)-6F*w_Rcy)QxDfgmpt#7B^q2;w6LAg3WH>z5J# zmfTJ&p}|j@1iK#jPh?TfHXk*AWuM$m(A}(V5{GR1VqWecE};f z+mI`wWDxZ=h`4R}bQBF#w)8iJTl!i%u`@0HwmKhWeQ6DWs^*&Rfz6$CMM{YHTkla+ zF#JG6e_zvsnn^ zb6-oOqOGkjym>QXZ;&a`D`X1z@A&Hk$770?t{zje9r&ApYpJB^ZjJ%%ac-h?(=>#- z_on_*+QFy(Wt|q>g9ppuTV7GwEIhdPMmeUuk|mLT7W!?u7yCoraYn(x&pKBuL z9jzOmnvCy~cckd9lJ{6VfBFU?W-XYE@0NEI>1ty7>3DwDi=6+D5U&DqDMJR@OVtXs zKgCiTh9H*w%cni)3Vp2oqI?5+Q$gJkll)G^So!l{_8{UC@I^h-1==5Zm;8v0kNzO< z5+~xrwhG{yIf0R9wg5q~Gd z%3lxuFya#N%MmXF_!-zB{vL>x9|8Xy;u7#3#E}8~sn{U?K8Tf1zf$e+**fzXCqZVX=^yp9KK%M<7=IN${in3~BNjzSJ49tM~9vM+)Mff>`-y zz^CyW3o-xJ`kMg&@lQjn{Bz(hMqDf;=FbFx_-7$j{w45hV`OY4=4T^?_~#&2{#EcB z5f=+Feuj=j`SDbXC{LBV#Ku zKacrEHvUfVUrT~d4_%P|QX3z~?$lFpe5zpzxpCun4q}L33vJE+QSdQ_Eg_bFYyJHu z5X47&#qmeLKZm$ji1E=kRY3jIHt|}-r#-EixkNJb~d_46dT*HWq zg;@JhpOFFnZ^H)p-wAE;?*PViLZSF?EdL&T1O%V*Li{~8ei8U@BF@KbYx(na9Qe0m zoA~>nt^OB-KZ>|Sd~5j&m_Kae*JDjf?VN~j?LWB4C;cC?@z;P~H38q+{wSy9AF=T} z!G|RDUyOJeQ2slxLFGRNZ7u&6@UaF+$hXcPWQ+JCHvSIq4<^CK#Yf3MW#jJ!|D_4| zS%{Yb`Nupj`KO_+`5y+K`cIVF9Q4Ty{SviL7YybR!1VuSdr zp{@MKz<&X8iTKo4WB|Vk8^jMmTlr6cKhgM)+62Fv`JFaCe0#`V6l=-9wf<|EAF=U| zf&ak-{5go10sfa_gYrKJZO#81c!>jJFroh(NyZ=WsojV_1a0NN1AZ>z66K%bWdMH} zHi*9y+R8rwquB1(L{Ir(8f%zjg zKE_4bD~Ow{{P!^bl#SmAKE+MO2Ts=CX&XNT{(DLAo0xyr#z)(weUb#fnfd2z{B7W) z4^5i?mCV0j%+s>`mu-CXXK!8-{Ck;y#m0XQ{E3bq*7jem_$g3v{pV%y zX>OXV{qAGF$HvFp=_T8f@z*e)>dG?yo#+pEPHS@hZ!tgH#>d?0MH^1)pXLaR-xSlv zN8j;2mIVJx%+I&+$G{&>g1?seMe+Pp)Tj4o68v?{FSYS!fPXRxeu(+y(3bj7#WBYF zF5)I@zZT}#+W5ubUr2(#p7{+nemVH4!%6+OGN1aIHUB|e6Q{f<%D)%!GT`{rh7D@} z5VVz#evytkoRl91hWMQ}KKgFDnFPO``4Jm`5d3*b@H?0fd|dt?0e_DTGt^C)( zr??nD=D!y4GJwAk8|42Gw3UxzNBTL$#rQG*3lT2^_}$nbKJpvKzX0k)?LS|!W%)NT z|Co*c8TbLjCGwwR;rB3q1lp2+mlIT)n-cNKt_;Y3FE%Lur=YET%<;ab5toQ>Eq{dh zr)_)_)ORM}=OJDO`2R9C$p2YrtN#MU6Wz&~N& zAI%-_ANiW{GU5{XpF>~@;6DIGeCplS{BH-J+9VO5>O}_d2eCnXKeUy<2Lm+&af$fz zB^iIfe-MiJ+0a%#`d-EkZWybR!P#RlB%`X6F`Lp5g{@uCYA3`P+RDeVbLtz2n~cAM`4Jl*?K$=R3HX>>Re=BB z#TNM=gtq!e-A%oOxJ3S~{70BSWaBsEadSi^N);Gal>Pwh_af7-@B2maX! z_}21&llg?T_J8z!|HUNudzpXE#=ieFXKN3T3T7y7oJb=E=$W-?y0WvGFnXrmaeXzmNHT8@~bk&LsGM zzjUBPsjGj@{e&U%WvBF-Qe$@fNvc?f5`lN8y~)>9Y})z6!VL0{GH$*OM?GL z%rCX^(XP{IJR~Z=HUC&A$?})m_y@uNC<*?znO|$;9|eCb3H~ti(cW?GH-Zl?P@kAA z|IaXgwT+LmXJQOY%BMD>{vWdOaqP`3OM?F_^Qo^=eRJ?&?#XP#&Sd#VJxl))Xe<8; z_+)c3{&$!^7|&00flq6@$@tGPf5^tq0-xrl$@tGRAN|#u|Fk^tUr2&~i1~YL{37s2 zlHea^{yrOjG5GH$!KXY@`wv4~>OZX>{7(=!S^57M7~)eoto$`XOm`%~f06k|Y<#r+ z^z0<~-(~(W8-EM<1xfHA{8KhQj;qsA2b0zxm7D7Cw2eOuek2M0 z_n3dy#z(oPKavC=^OW>|&c=Tge43jlYya;v|ALMGCirA~GXBfVzii{9JkzO8C*%Jq z^RL+WAA|ov68u*bAJavA|NRtv$fW-NjQO5;z88Hz%bNtB`Yqbu<+t&%pJgV&|8wSN z+xWAQBN)L)Kb%WF`7#r#1V{|)dDOu(OG;h$vw5VY0*JK)1cLjN_0 zmjU_zYiv;dDX&)kd3@Q5@}4OFrIL(4;FAyHQ(Ig4m%*<>Tq3>)@iKt_18fj~AGDQ^ z-a4}$ag*^+F@HFoPxnt|Hc!B}_WvI;|B#K3Hk#Qv0pFT`%+)ggM{N9j@TpHsmjD0G zeDoJ<`O`7~%%nas8UHQjkJ$LA`c0p4iTZy)v1R`wzr;Uh<8K51Bg7@jzqS2-#QY03 zKFU7xlL`3N{&R-;mu-CX=WIkJ^e+=A5#|3e6y^U4w6*-tfls)}^8b$F(>2dH{>$K- zh)cw`&Od(#4E(#PPOSbB-etbu#z(tkQ#&Q{Z{`0z^Qo*>{~v%~Hv!*? zco|TCXR$%~H=(V3v|V-(ag+J~2j=J7_+#MX7&s~aJ?0n1^L;2|_Sz))|H%AO8-E7) z?Md+8XMVYjKL`AtB>4Y>`L#BFG5A}O;G>;n{WsY7<={V*1ph4Zt{GB#Fd}kj`g8wn|_t^MHz(1Y@|0m4fXXC#H{)r^`7nnb6 zKlUi(3aT?1!2f4#5dSo^m2V31Iogl$wb%G}$SyJe ztc_m)KE+MO|0(m&+4yDP)7YAff0_AICTsrd!2cu({?C|CcC7r>;G+#D^*_q|D>i;R zeW#wA^_k-_E!vCktCj(agcJQ+%;Omy8^~b+3pL&OtzX$x>B=}TE=s%up z8~!2Q2)Qg2IapT+FJh~fgeQNWc}wV^J{H<^vj&pN$~%T`3>>>4D9BtkKd^XZRMlxawsmwPwYR1`Fm{q z2>6d7j{2YqiTO?>QvUZrTm2*NIZq(2R*~^HF`sVU5q}uk%HIwCe#A9HREY7T@1Qvy zh6{zAq z@nin2{U?q2r)+#2`*Sdd$Y+e85EAogER+A!&{qHFz#l=}4u}eg`RPa`{#j@%{}T9b zAa0K$<8NX+BJnRkTliB^t{l{Xd^i4A@iUlz*~ZTX zpUOmSphC>Qwf{^-BKf}pZS_AF{BwvKfv6B`e=4&K=zkOoei~V~@&n+1g18Zg3W@zs zLn86Xj+Ng4{-=neI#eO%KVL_|eLnQQX=|B1fS*z@_#IzKg|JtX%hV3Wd4Ya?+3peag&vQE->W(l#P#m zkz1Pt{}$$-w(;kK-!K8++W)AI$v@i5+WylPfq#Dz{9BoS&c;XHb32pZ(_BpcFWC62 z!0%6je;f0uo-O@%S{wL7N$^om(*G43zYqM~N$_u1d@od-{~_@AAudt-TkF377?j`Z zvGE@R|6mgQLgxEz{HMS_k_5kq`Pnx93*ci8nKb`*FyFNCkAZ(G34Ss2^KJY$z<(zR z{^yxb<*>FtxVh(&;4ff)sf~}hKlhU)_$ACQxA8B7e?VH_d@48fztuKAj>|XYCc(dx`5_x0?RgWmDe8fPhjoTt{ot;R!@xWv{dYoJ z+AkCJauc=9Wc)8OKVsuofnS~kzl`~VHhv@c^-1vWV*Zeg9|E81bh7*}28QZ?r;XnO zKIX7V`Ae9;$Hw0VKGn%&{>z!a&&J;cK5R_NuVDVLjSqV_QJE(5A7K6=8~-`*pGbmV z$^0WW{>$JGC&5QQk@bJf#y2jMMnLV&mhuG|QI+f4Sn*Wr#Tc<=|%{ZnF9h0t5f)sINFa;%A`@3Hfrd zfi#*wS3nWp4{a^~{oofME|GsLzk&IbS1W%5_@xu@(Kaf$9mYM_BL60|l|Kl65OIn8 zXCht(@G;Lyem=C7{|NXY#3kbAB3=gYo3KIti=eH1%pJ2Pn!mCX8+UCSMlC^W=%T&z`vLI zoi_ew;KPQ+zXy91Ecc(lUyT^@AAz>`&vJr4inv(*V|>c94B+2~4dSD{h67&6tmjV2B*dYEP zXe+-ReB>da|7=OdAMis^#6JRU<@bZX9&w5CpNn`Iz;D3@@sB}U`P;#N2yuz{x+jr; z@6F=(;a2pB=sRds^_mI&$Y+v$SG)weWVdYzjYs zk4P6tuLr+%B25c$4iw}FzR+B2cw@hgO1V2bI^#1f6WHqn&T)2h$!l70-$weaT(Nw) z@U-@B+SJ=q&?)eH9A_`xIjSYjS~}&f|G`LjGl0PTG?Jr5<7yhKQHpl_$~JzOubW_a zc{>@eDcU1xZB5omyjPU9@F<>l=Qw!#NVyiEsP#8C zHCI&`p3VJjZQ*uRy9aOo%U^1VqL!_x7M{&rTf^)6HEJXewxnp(y_K~UBJF|J&Xzs} zGl2NM7L7u_mXSxDd3<>Jwr{+t1;55C^Hte=UxVRprOqK8Qm=&2U(%Vp7x#s9{LX20 z&lb}pn0iGGD7xE4-~D#tU-S#<>Nf->j_yN9%c(J&4FUDd^_s5k@Up7LaJ#fg_pJop zlu>9o$`Fw?4PSu|cZd7K@o0}kXE8cYX_1xL5NHfX5nc5)#j9t6aRNk-im8)my?n{S{kUy1L~rBoYqcBqP?d_PEsd=uM>2 zYLULBD{bN121@(V6{4~m-@ivX-7ga25~pru?azpQ*IoimU@{131Q_vx6{)^$A4SqV zAo>ca@_3YW)->x^@DYL8kCgBiNdAv^&_Mcac=+9e?|R-jJb z*Ne^!z)cd>$S7UQK2JjcKZdZ1K!lnlQ6Wa@8YUDRMp1PCK{$IDrTeqb+YpeyS3^BCoEH3Y~S*|~-6xOx0Lb$xl<$P+=3m`{kb9g3T}UVB1IS2g2T zDwehO5{j<7(^ukT9pP))s;;QlR5hb;j3SWmzp7HxiZ#kZu9~`g`&F*#8ajPNSJ}{I z^G|6ExAvm=YJkXkpD&S5GV(pvglwUi)q=cTA`dcxYeZ!E8HwNE&|nkD0z%`vpE3u= zQEk@;bXyhuUPsrau*z#8A)QqU;fU4_dJ;A1vYz%{6M!O#dYMrN*dFyaplURVu3Zb~ z2}ZqOLD7)XD0#Mq_cLVPQgq%tuEuZ=7F;Gw&^2h`jwpmPTFYn)c)PcX3<0n}q8?(@ zBKAhToAOi{kp5&X(=}z`-p$BBJQ9<147pGu_cOB2fM*IP^Y9+3Yj65g75apejDf_bj|vGO*9Z1kTVQPH4f;yrB_dyo6e3L{d}*>a z?(L-?uLg?Faz!T32;NNX(bq&jZ(Z5h($f*<6)IgT6wZ8waBwLwsbdWExAYI-CxuJ^ z>LuzEM)B+*k5$xdSB3jF6KXl3@TEMNd&hb9O2-x&%?-Wk_lbfMRm`Z178Kdis1=0r zbgNW79|?a(^f=Yf(zi)^ZIDQwshwxoo*pe#U40d;t?E~d=(?Qn@J#J|&x(X=E3%Q0 zcxPOe1iyPBwIG|-gE;#@_0d{FZXx38rY=QSI>2ZI5+Um~|D zgqQmg%`b9J2sGh55(v5uB{KIhO4qi3YSXfwE;Z7dsn7teCFFGOx!%3(8FRw2p8m%0 zCLHli0O-1sm|o8I4ro+5tr}J-7yuzcW$+kw>++)2YA_8^w~=LSk*Hi{!xL>=FHjl= z#CnOE$EaxA!WQ)|ZHunFhzwr&c`|jr$d-ly&_*cy4vB0Bu`7lO_tUS3p~_4ElPG^mAI;elUx(XZiqvez@(mbW1W1LDgP zbxftYEGAM)knnvCOEs^ERS1#{u zqaH*xP1k>AExU9rXJ-*=Sr3k61FijCy*-sJt(|ag0z4oYygGG7>u(A%Xt}^ppteZl zX|^4$Kk88xkw`a9CqO(PQDcnK^(TAO8k88RgM{!EGvcDsRFZK2(!SmY81*2brtDFv z?x;5}enhW7yoo?2At)GZBRa<9Sb({}fV#f(+k|oyDO(16#(D=M23wh_v8T%l5Q z&V3$z;;ZV5Kw=uVeA;%Mdo@ef_x7l3IS)zHDwUeHOQX`8s7tCfmNbMvKA!zt(Xr)0 zZ|vI=e;zl*scg8{Yg=h@Z75IWOl`~azDmXK!O0$gTIF-Os&Eh0=ypQln=~>t)ypWl z8YR#88fyqA`-L=_RiF^~0<5x!)gR6xu-XaqgpWcUw~1M63X?WLZzJ0wmit2y51o@5=h|Q zt@PyGrBUg0Y@xA$vqS*CCQ;lcy!#o2a?-JdVXB9ktxf%5g(8r^Zxc$J>D*!%Eh~Gvdt2Hn z7Bq$F`1vhDrEu*UV>*9nDwUK%I1G1ON2=5w=oTBp9 z(A(A1Pf&pNOC-;=-u2qEm&R1=I?x|V)F&!c_d8tIkn1*d^Y&hx-(ZcQygwz;Tnevl zIjk^fJt)trBowGWBBX=!XVmF@(%5fq>A-y1u1oYZA*XPkaUW--$JN{$q4|qaDNMhh zQqmpT)|BRI6s4&Zza3hSi%}*(!w@n3Ge&wXNcfFIK10Z?)r<@<5^dayyGcFhiPGKy zLgCqQgS2N%+w)P=>7Q6tHS;VXGJ6!l8||@E_xy%_}400Hd>SVD$RlK`X%o`YX)AC|K+YwqM;wtz zo)^+|O`{ZYtjJ{)5Z{xiA-1P$7$`a$LN~)=3y7l~Y7=)CeH(RENw{dL|=$>CM$OeZ8CHPzT_+L~*KKAES>nSEH0PamW%7 zNVv+Bjg;-$me+EXUPAC^%10{I9c=?o{1ILPQ@E~-7oxWA?d$3f1 z(LYb>dK}%_ zVSR;8Ci=Iiyrt=S@ulIowXCNNi?CQ;T<LbcVimor82lrs)azOQnza=}aQ;a&JG|DCK zG|N+UvUQqJQ?{#Ax2`>(mxggyYuuStMzcbB@}N`k&*NOWqH39?|KZ#0vTogD&?)$b zlCNl}sJxpz$&m31+gJnb!aqVR3pUrANPdSP(R(Zx6%u_|o;NI~%RhvYVd_q{L*uPiTu_!!5e_^;-AmES*|JqUHzNM zj_h?A+;cs;B|NUGW%XD&65>6G$XKsD;J4Rg{h$@9mZ{aHrS_&DQK{bZF;sU)LkmuE z73zJmHT^Y4#p#D2+w!=p!e1T;4}_8U0U>;YD#fiwtKV1E zwK>we*?RYjfFkuhg-of7+Twe*GMWDD=9Bx^LmRUPxNTnL18kJuC<$)HyF9KUXA$#c|mFnu#sI;o)=*gn? zXuQp-V;Yq%Z{?^3JW&5zB6$R*Z)cRpUD@B;u-S6gVU|is+o0{FRni@3bX~P7{Xf#q zeoobG=ARO45v;ysjebH%{GP3>0e#&M1<7G{_uh&H4WkRI#NJuGWJ05pUldLTy{!Aj?QYk3` z3ql^5kvd8U{LZT?*8xWOJT-i)M~$YR6Ds{VmFm*n8BGW%%)CsE{z7|RVkyWNr(shlhR>F#fZ`qo#C67+{{-r9xUbcmC z&^@rZQ$C6UlnVG&A&GLm$0&?AoIYdv)`;ocSH001@KSX)_tIYz-qHv;{RzeQ>KgE+ zQ+h>D8!4c?gi1N4P>xLY=JV*&LD_4JbV8-Ft&}{SC(KOxX%zy9={yT~M>WDty}37n ztDD9YLS*b#Df;;&dE~{N(yn&3ZZlxuoqbGi7D9fXqkttD)jN&@S8i<}AilL?x z%Ez;$yI6a3)yju{OyozNEBJVBcQ4Z3Jhj1Ig`r%l;4)RJE+4JQ^i@px6#(HKQixPt z&$9Q+yPDRkm|lhQ=+gT<%T_KAqeH4E1LPb1!p%pxlrfzr8oxLlY{#X}Xr8j_RBAfq zoc_u2i9VuOtvA%ZtVK2F-zhe;?!83)7iT0NwTNN=hE;?TGCcZ zv>$k=7NS>H4U=r?yF@9`vJpx>|08YPOsMn^lr4R?0?(z9M_R$hyvj4wDaRGk{U+zp zLBh(!op{cd<5knKT`kNBL_3n<=yStVazO5T6 zl|sfDl^6W3uB;Kt{Ysixtuxsz5_yi1eHuAMAsaLlUh^gL5+k>2By3VKHP8g|R*B@J ziD$b;!X_cDHs=v?YBt;4%Sc~3dUs2QG6{RPNz`0M?YGziYFU+b4di@@3^4KqjYJ-4 zji!Mnkhe=@10#=WB=TrkhsvH%Ad%gSJYhkSO>1ciC6a5#!|jdhk!({#;a#rA-MPw^ z)4?|BsT3MJ_g1O7`VO+E+t=yUD0;G!9#tY3Y!wsAbC7MF*C^PkU5>kv$`)a8HfpL= zw{B-S&ah~0pk)zkEs!Wa0=l*FR=_9%ZB2n_$wNzenINr0MaF~mt%E_a?x|nj2 zQF2YCt}h!EgmCj1cE)-_^bV%<79f(+UTdc|Y7{+_t6^x9DnfbR zQK_yxjiS|{{XEA%tWxz8Olqx1lZ(1mZPbu0|Hq8d`SMSNt@vxz#!^B~^D}ZidzEXx z_-oZhEg`*a3XyuAk+P@qs{6IOe4*oa-f#r+-%ynYwOjHEW$Oplz) zRQ$;}q*6SwdRw*<<-pxl`DC|IPsogujM63Wr7c^D7S||cE#(b`a^$c#S+MayQL_Ix1KoiIYY41%&?qZ~} zM{TR@-9soJuR7fUjq=dytPvN5RPSz-DDKs6-L5`QEl!4`D8k?xf4;Keb+9dU?8b>| zYxnXFb+1mo2`Z*OsSqALTKqT)sAI*7K6z6SrC3Qe@hjT~X~!D2Nu!U?JgKLv;QC0V zrkrP#ymzgxQp$HX>6eD3ozyXH$J^9g(b(M7ys{>?D)I$Ysw;|0U*0mP7a!UizS=8o zxpg@`Zn~gXTic}(1|KRlRE@Y9YL-8n+(!J}`mA^g;G|LTSuY^7heceQrlr4e6It zq%ALx610#+Nsx&Wv`f?hM!jdX#V3G->L3*EHK`hn<||FrC|{d0Iti3=O`#l%7)|dh z(Csu6c4W`-GQy=1KI(n4m0<~}4P?i6LGj(uT1{V$$2@VV0_PGysetnnm8ZSh8=m;> zQ)k_H77rnX`;?zzPM=C~1S|-?E@kwP4d*sSY-a?GPQEg2ny}L=Q9J`V zSj!`*zQ{|ch(vLpa_qO*;-~+mtuIT|7<-Ji8=kF?-UWcIK0>9IGip@Z!qYOpU_v5s#DCclD_AS9wHmH>*@~w}7AM=p`69iWLnj&%Cu?Jnyn4LsHCnTDgx3uK zd-7Q`5BCMzWPC7(6(9A%FewJp>?t$O9dxj=1`wni9b zt^ZoOmcjM^I}-Iaqqt3FIcuq2EnXiYq<6kTrk=2Pt!m~X!0T5Cg_o@``0a;o8*F)q{7Gu|Co7 zCzBTc^^UT7zjt)@`m-#k6IpMu_q*{xka~AFxp$Az#Aiou=+bh%Jv?GNAYZyQ&i=IT z2U_BA@*Qd{^!nx7xyk!Y?hS7t{tPfGl zH+AB{COPl9C6a6sl5Enytmzu5uGojG@S>Ub`jPO{$lu@%aVffHZEuOwaXR_YkX+6F zdf(Vj<{kC|lwtC>zzYQ4PM`F>b9pTTb*y>@XVger!XJ(up4;Kh(WOvkN!Ht-FJpyLuDDSu>7Ue8qa6x4BD-#NH&%3o^{ zS>Om`$MWjs1tN7*coTi-A=zFdjK`#JI*X$`=(%2A8?X#BnGvM_D@LL!IccgV3i2$B zDzu>=hhKT7BTnIMd-dE0X+o;I{>b2FJXM4^O#SjNy_t_w5-z>R1w?CvpKYNOngdAF z96-V224WMAYuJ86rqI|YgdVHP#-@mgIOT=fiV)OWC@4f{pNXpas*Iy+vy`@m5&kLE zOB!$yzr8EmE#KrZepjaLX5@NC(mgktlY0AFXw@NpD&zMvY6GLxOjAQo^taHX_3(_} zWFU`Zhjg9PI6rWcJ*>izw>`2|C~wp{grFWGTc;jNRBj<|9h@gotAHl8m+kqd(lWd| zObvv`W##qNzgN#E06}+xsP5^Xk3P!K+S0!c4?38c*Y&!yKfC(}S|hL9W8rF3%=-qoH^ITe~%>T9sq~NBl6fub7~59=jmKe)5p;Ia7Rmb^z7g} z5=C<>p=oZVe_4Kwrtzx4CTu?|QB+1k=SiJ{b5okHQN*(-b6dZQy- zl&dQSz+mko1q#Qn#-27lsW&MinnEb5hiUldadm8#RfLG|Ax?;M(igpvLVK{|t;7p_ zyix<|Ycif{oYM28#tbo)R-Eb*Tys~yJR{gA5o#8T&1QIhuDiE`ZWXBFn%B=I2XEUQ z*1}&v+Cg7H68r_ER%9dD^J4uaCDLqbqt%wYfQB^?%0ka|lZJKlq)etNN0=M!(Xa7LQr3&K!{dUu)K06Q>exWkz+x$ zb#-*9y9ks*vp8w0eF|g)&p1aq)gJ!RF@z9givl6=;jS>QVd2+XDTR(3q>C+xNFPp~ zf%r?AvYrvTZgr;YW? zmg9S5-SlRH{0tpNS`yzQGwzhNPJN1MpZWv+^QFsKurb_*r30)}8>!w)ntGl@rO}K~ zv7T=CnLxcL=W^<~G^f)jh|TFV^YNTseEphywE9oavRuDrkk^IKY{}0Z`Wh4I3GAWa zlKjRUE{i9%R{8(%rM~}%ze@O@bvBdSy0HLbGMOtxxWZ!mP_Be&)}|FP^+x&aymUHx z(Kp4Q5cgll3pMl747>>SLE*oS7nUzh{@3xs`u)lT#|ye5_U}DjBsWS$2A)36Ffn1z zp@kZPDRP&T3FGkP)OT55WPMF(R}Sk2))CfwSszt8ZNAdR2${8nxx783G*(42xI#)h zXuKe#$Y(U6<-I&yMUeNVRGR#o&D-UyL#%B$L#JO->FWL`X0i9wzN3!ye%7BUow9-T zn9|q%oyRe|S=({Cob{Y8@0DVEAMbOy^{-(WG?yZ%XXl_-@xHvrhy8bW|De+PFV6Sk zf`fS}YgIJ9Yam0aI>!-Wq1wmQBN>cOly=Nu-NyPU*6%CryvX`9rRjnrg;dUWDz`@} zmpfHH1BmoRjH_ZDWPLyDcGmK~H1Jz_e+TQwSaZFlKF9kYWV!tjfuWOjoxqV!3@ILn+yd5NiyPS0!>nB+cv*vbjvmN&tj_3TiKjwX{FY~_E zV`|^S;{o3VlEIV9dM@iC)@7`@zj;>kemm>uSaW{x{H6?eDoE*TN~du=iuFsZ zM_6+`dq3iR?yu=wk9bd5hIFpS^trsx{_)(nO4oWT??1tMAM5v(_OU(RV&3O^^6lsS z)2v68p2GMk#k}9hx{vh{);!)bylOvVA?pZh&QHcM-v2=9sY;2dChv27PVMCV7g?WR zeU|k_)>oDGb3Xm~ywBzHbG!JtANYqjeJ|@ntY2fz<-pU5GWf43J&nsXjoV>b0mqlK z=6a!Dy`nH}3+tV%pJx3s>o-}SW6kYOzZyg#GmmvC>mX~kpULfOEGm;ZC4ywCNJ<5K%M ze%9HnP1gCWi&%4i&MD`8uAiJ{-e1c)#JZDp4{L6>9ImGvZoeGv7dg8a$L*NI^_9c* zmBaOwbBJ+ASaZGQyvqCBuX0ZDKG$Ck*Gta(9Dk1W1=g2Yf2#D2Tpu@@yg!FEm;1&o zywCZ*k@I`wQykCvx{=%Q#xoqx?Q-Kq-v3PLTnFnc*14?NZ|;2FU%r_} zX4WCr-K+;$?_j-$^;4`5v3{BL2&vXKDm}}?I*YZ*dLHWqtht?M-Ou}+ zuUWf!pZnviZ z$CSo2uea>GdCGi{RSez&B&h+%)59no|%GNw`r zUZQ$BoMYAx&#u3eEyhR5-{aTa8$x}5T>BHlL$xA?P&U-*;cZloc2{IPB*cl^yM-V1 z(T$q8D(mL5tea0|-CUM+f%*v^Pkp&y_mYC~F~}8amBTj{>^^)0^6CKQ(&2CVdW2*A zQoHk^@lZRq4Q!t;^ucQir9&=~t;64-l8;}c(%imo{9_aG!?yzEmGN@F&`+raLZ;1& z7)Ksm@l%n}=B-#SA}uYHZ(+5C@&)XZgcsMMm+iS^iF#QP@WF< z8fXp_h%DvPT!(!5f~WUX(LJMOJrVgKS3JS3`uU|opRDzVt+n2<=2zEuQNj49wIdH- z6^^^gu|+*m*1mJ$1hwx)I%=QlLiM??MX&M8-gdRnjq>??4S3MqMR^Yu1Ue|DFkcY& z>f!l|XZx0-9=vA3_|+pX2|wdpsy+&42iNM7eqG)>!1KSdkmy^me%Ce{N%HTtheRk7 zt$UT)5B;Zj{Mzcr8<)bmGkoE$ZN}Hf;}A|8f;>?>;uZ#t7P?QYYVs=O^H4wf!`0eO z;Xj;DxUa|E@d&O$X^)EqD47Qs54Ml5pVhSqoV*vUYZE2?lBK*0F-G10wDUB^qgie?Eb&wM8z zcx9biS>(xM~5e{}JWu z>*Oo?)L48Aa{B}#DSMXo;il0UDpQ5@mMgu*#V7KpT9iE=Qyw29kFlqWREu6&QXqTZ z6*&)}hojD>juZ&b{f~FZ8k~)7N{cTMwcO*D643KBvc9fFoTqz)cL~kI{|=jb0yhN~%X6a*r^3f32%Br7ApYhLINuM+aPNk!*XD-J4;@j{E&5goQcAIcM z)wF27aP99Fj?(#fjk5W*>PVxo{TE7mm}_*mB`GeVOWESL}HPSvv0l!&@(K=8!=y+^nUQwCY^K4CCE%djaulfF|4u9S{b6wHw z!nEN%OW<2sK&p{f8*-&gPj?ssd;as~P33{ruy?f>^Se3dc8a@RbPT|DdftXP*v9?PP;Q`0GVt}E9)E`iu};Lgpi_hcX-;3Ma2SO> zUVo`k*jPTjT)3sJ3{RFU?=-m{l=0{#(UZ=^Qcd{o(l4 z#){8Z2yb1f9x^iJHPo$~oXhhJpW4W@jysS2Jmz%C1P z+yl0`;*LT5GI4gqb0E|{=Z;SNhQ^Eo?)IFUJMnC3(aqbc8<*WJaw7&(U7zp7^BEcF zWn0Z28$$Zs4P73E^ItYNM&M~(8lPS0pr zRo=QGA?iQG=M}%uDS$=YR}FPJtA;{nkSe}1k|jdtMrZF3f#*kOo*o*#I$zK&u?jmS{py&?;x$qEk8QGnJ{htNw3J+w9a-DhjQ z_hE#l9%SB0OBYz>bGPfy630UoUneBU8nRvs+m?PPWqu|K#< zD}V|48RYkw?=o+0#h6%FRCe*6Jj5Q|*Ws$$*O3|eta414KRzA&kwgsz$|;9BXFxZG zF5#zT=xwLaCVV;%x^44=o3}vkjP&)=efGWWkpUC>X?#GA^o5P<+n|k{mi}&h*T4vD z+C=F_oBBqev9lW+*hlrM5HUjA^DsIhPG1+kx$Wu5^mXBla8J9lu{`ARm-@bc|4>Mb zhj0ukHP&Ej6-KMjJ(>+><)45%u&()8O3@9`yNV#cAz&DpTD2y@el;m@H* zW}r>inX`xM3x?FzLEd`iraX_i&U{$Ke8SHfPfu zJY0}h5&Tgk8?z8**}`7jlg>oxX|`Wj^d}-4x)?RFc=1s1bP;BOr>}}xn6a~gF%VDN zE^}S;3K~P>Z(|n2QCwu-u?yuco$f5Cd^L--s=6i&~W)Sv%U~#PQMe!cs%SDc~<|PDjM~L*`Kg+d- z%B8aE04^2z%aCLDkjT{*qt+A-rLo{Hh}_~s!n9$xjsH^S)XgQI5uXFLs8O(pvL~(| z3kEgg6EkAIE8sgaW4J*3&cjjknHtr$MYGYKOR42EXBW-Jyz6|vCggaQQz_0|N3qW> z4LJ@-T+wWuN@+Uod5gX0fUCG-T#1Yim#QPR>@|0yKhW4%XC8C-fD@Ph-m{#L59_1F9buis3Kj4as$YFAr#p;_TXT z|L5w^=Q=X**VW-XSQhdRmxX+4@3}IRqRfkNJ{$9`KEt81o9l>OnD&3$dl&eqs%w9I zpEGA3nGBO5h7dy};}8QxNJ5N=5h0m82r(of2^hKFW)kv%5Rw=IBBgc`o}!|qUav*8 zky1>kMM|v~DYb!8q?B5u)>>+YnVhrN zT6^vHd!4oSrnGiff~ztdN-qvTH$v=fkty<4Q{P!OJAI_D+CLY4#$co4y{dA7nSk7v<+|KAI;_pDS-);WJy=v%2Iq<#bS2{xX@Mu+2~utiJ@*c|)Fyt}VljOE<$ zAT|wJw+N^^gv>LYqJW|dp{Ss!Ln!E1cUXo{G*Gl56e}p!Aru=ZwjmTdDE1+g7*Jw{ zP)30=Y6#^zP_7$7ae(3&LWuw+VhF_vigO4h5|qdx6c;G2A(RoIj2J@60VQV$#Se;q z2qhPk+#wVXD4rpdk)VtmLWu??dI%*Bl(-?3JW%q6Q1U^^mlU+Js1W21L8C*^tpJf z9eOqNxtRCNJs42S4$7W|dlN7G_lP+7<3cZ)X)gMXVFH+rbk{F-3mFdMAkV_K4HneP_jt-*l7T=qqIG zF-F6OJ?z8K{Dm*wE=n)@OfSdBBl}IxRdUtGu3RFzI|h6n;-S#UXFbl6!^tsopdTIu zJ~`^hG{GL}hYjKnu5S0=upj+5?5`VUFVh8`IWE^&syE$^2F{^wSA!eno4Of?5P}0wa32hd7KjaId*8lkG^hg$r=V z8No0PJ+0A~hok#XE!db-$g$e&gVA0lu|KKZdEl1w9&q`pAuGVB=Ur(jNuOr4QJ=-kl!AYOs_1;8Dg#>&1Z9 zrtA#J9!aOFOjUY6ep?hEZ=}>Mg^lnM}~~Bhi_2yyJNlcwIj5?XAlZR2$I49GLob)E9S9hmz zKSe83U}?&8X}*o&RY!VacY1mDSbgOIY>LEKlIYN97$g^>^(R(#SOq3**GQ6y8Gg0@ z`sjUX0Q2>g&a+-e)7ZAGulO2-c&~fnjYpkleP!cTTBeK4++c>1Sz(5w7t$NZQXmQR;JZfmT=;+TV4G`8U(O$e)}0#g zr%x1~mVWPSq$f*c+=z9NNW$ACyi+)x6t}&I8)kQwK=h3{A_20fDhh9hr6{{mt#R-S zk5*_%^`47Jg~wNRe(RlPncn)b zH@oCS;rs9Pao&|d>Uw*lt?aCr7FdK4D8n~FBs98wIfd^BgC$=m{*|J*vPD*dtz`e; zrU0bAn15xBs}c!#lJJnc7G>04@OVyRbcQJG^A&D^6s|iJd6*SY+p}aSQ;}R2rIJ{*=_?lGxv%Jw5@mhuEp9{MWx^F59wz|D#w2I7lis%`SLrMq< z1-{#+19$m*g&N{aPAaP`!g%Z1yPze$B4=r=x2R1$0lBi8JvdO7{bpZxYDG@qZ1Jy< z=_G5Q`Xj@b;4P~xq>*}WRZ9X~#x+KpU7qNJyL0{?gvIia?wT}~bnnp3=V~c#zKs-5 zUr@?Cy6Yv+a}8;QB$@rHZt|LYv8?$thIcpdnp*4b@>W+phBhN5n3zLjp5qFwol`M~ zcNe7!C)UnoSUZ%I-ukOA}Hz&h>A=Lo*Q>W7sb#t6<-mIGwbu&dbXX|E>ZZ_)XBKSGAWckdM zs+UrZ9_01H_tVq;n9Fx4wBA8$dKEe5Xw(3G7d0^&YZIDmj|01HTLWkQMu?(?nW? zbOA0GxI88<54aEmUt&@s_|!B7@@OA&;|~-%V5RpwAk7=3kQhostQtY(o(=h!2DVTWJ%_ew~37%|%Q%!K12~Ib`Qw*4E%5qso46iW3ttPn5fV=MtLG1)_ zO)cyFHMJuj&Ey>P6~OrRkf!vW)+MmiBGQD``y1dMYDK<{hD4WI0J-n|E$q-nXe1|I z;)0#4_mAYp;I4`5{RM?l#Hk_oOLbeNQSTl(~Y z$Ur=CrA>3bG@P#qb13JYfYXn7Pw8{+X%l)nS2|zno_J5`%xTU&{zmC4&C@$jSvs>C zt($C9qujafKp<=3oBt-lZ6?ANM4NIO0Ma$&w?@F;jpmuH^9|aM4#y z#BVzGV~-8f~7qH~In}dpzaYBqfrzA0GJOf)83*Vc??gZQtXDYwGzj_9-gAwUQJ1S9JA-7;p8+UqyaI zbI{1*k`wbD!#8Hv%rxOfZrgfusJ=m#AB1cM680&-a`u;;=$R_#4QVSQ&eI5kzObo# zVc}AIFK+1>=#B18?>!D(!_VHll6FZ!w@(ZXVrG%iQ&zH^xJWx=41LRwsd|h@`}62Q zr}e(%ACKH|KuhF&Z(C7fV>@)X&SM7as9l6nf8~knlYfHSH^?5d^i@@l1y<_>wLow8Op` z=pd8%mx}n_n*w#H*Kpl?ryk!*j)nigT_=ls&d&!oP?{ zHQ?hB=_u;|c*F1BvB){rUFwbXPc}5{pzR}5g^O91)#tU|51HZMnZd5XMMzycpyNmt z>2k(G=WPE#CY~ya6>=vYBLuYMXVNqv29V$jm7tR+wV3`+~~mfZ-RzA3WTHn9n{IACHpNG zS5v)PfE=^rev)QKs{FQ~NPhNvj-W_P6>XW17m)r@n&P#$drtSTE^T@HEnBkh%o!Yj z&&mxe97hK~p)fi`rgFo}j-wNf5_ZGx!A~4VIka2qyAiAGRU+U^x4vI^f_;>+f*b3b zh?&-2cnBJY3ZmTH3O8NuRhHZm(iswDb zzsExPXPli#rI%*sZxcS!RfHcV-M#KaT`%t}NtL@3rT*F%4PQ*>D(dy%eT$$n<(jZx zD;O(g>=tevo0cG;(PpP_1+rAcVNa@XBlq`1CFAA0MdN3haAPRU#q*^L>)!ziaP;&f{I*=#-R^s_m81^Wm|FxDBQ_tQ6| zW`@qmk~tF6Z*z0LptcXZP3@jzJQlPNU8I)Fmf=DBz;}Q)#i;eDYx2ZP<5VqfPlE$S z--TRsAs4qG7dImpQ<`wZJMyn9U_(Bx3Zx?ysbT~)GGD-mmmQJLTr>%K zy1tos71YKngOkLo>zmzacG&C} zJ0=8^v&$ZX&2^)9(?j~2 z$d^Y$r)Y-V7J3^zdJJlf>IrF`KQ47H&B5{*k>-Ld2l1nR89f5` z5mIv>djs_T{qg*jZ8#%Qx~1E~kg1d>s0psdf+czPh{p7$8K~Xy{FRGqBQIF80!L@w4qhPY#Z}QQ8}PZM?b- zxmPPX8=c>DG+wM%=LO55AJV2^9dmv=(Yj9$zXlQD~cW_scn0ps7HZg+!ljs#z7)oL_UN!1q4kF8lCQD_5(5k7IpPh> z3PQ2^VyN_@Y$!9#eb$61D8a&Dd)k)lnM%(ZY6lxh13IMl%%dcvfeo0E@tmbCFahJ` zSi;Z-bUk!vfbSnT11)oxaNP?%|5xFn{+(3J7Rkqlq@Fg;OqO-7&DD2axG9G4#hHkD z1=l^qscUqbPFF^LpTVq7_+iF-}5~B zh?yrUNn^Wh@(E~vlg9Q0-mUbA5mP#Z)5sgrSf^S}>l^Yr`&0c+DMI-+K)V)YbJ<4}*}5BfVjp6Kg;f>m;n!hM_4&`*61q@jondWv9g z--K%H<(b}ln~u*WJZ*Yj`TX+UIVdMyl_Q5lD#BK0VUL*Sy%$#x9!E~6+n1zk*`l*C z4)c%fCP%eX(a5?JMd|M6U2^ zPsZ}$yjHq01{Y4YEay=Aj%Q%Dm)$fYFb%6Z z^u#5Y!(fh=N+$IT<=L36rD7Hvwu>IZsnM#J+V`fsR=Qw82F&vT4$Zg33;z=g_cOjO zqGwJP9s{R!hVD8ar*WEGbDtkU``PgQP!tB$Y;l4(H-t`9q&deGxVTj?7oVzl6)ubJ zmwvM1Rk)y?!XfIXD_(``ZJkR$Veu+lL7huKmGCND@913mNsCwEau{6lw8g7%*>o=b z#Ko&{W$Rq}S%_ERN++(3#>tD^%bIf^-$q4~lS7{fi@px27=<|3DyHbDqJ0BJ?9@@1Dk3>QUsX0_#wrLRx_F-nJT_+LURNF>cF!&EVo)yFEPt z8})^~?JRerea|2Ed>wK(hktzUvZBO>)4FL}tp;!-H0;k}j@th?#^ISfTLlDJT2Bw= z5pO3~_naaQecrn#EwbkqED5ZhjIZiQ!y@b`{s=SO0gQZ;g%c0Pl(b0db17tcxCc~w zt*MEw4MBlfiniJkSnZ|NwjfE<8;EV&VBaj5{TvqD8o8l9!ntv&2t7IF2Ha|a>>`8$ z$Qqc+{BlnIP2|9(a9&A?1bCuN^!?%lRxql^hTYVCgKtp@>9R-Ki>jYskJ-st4*wGJ z5VNV?q~6gJsu9u!8c*2fEJoN1h}qe}eh>0)4^qyta{~J-u*k*|ieuwgxH^X(93ve{ zmi8^4({u8h)R!aQAiGEW`oV)S5cN=u!S@S_dn4^gykKz4@rq`)jX4YHjQ=vZDTw+B znn_V>$H=GeDA#3F>|X+}G%2*k`AaIXhD&9+MG?y_@&Y6!!deotQ#MT!6~qN&8?E%% zK9MAbBw>;3MYu31uy=oo-%+~Xx@3f&P7Zi2lVG6}Km6B%RA46(Dl8hMavvgpsG#_R3@l4Jc|7f&g58>3>aDKov zUQa*1kyi>8DXJUl=RfE=_68mS;74>gUIE%8t_}{|Fl%s-<2Y82p%|?5qi+It zXr4{>kks(#O)Cw`K)$(y%Sd0vxKU$By0;CB#-~sto4H0#(`BUV11(fuq58;@kjZn& ztq*g!nInxFNo56z{=NSYcPeBydW|OcsNo3)r7C--F{2BmN1x3iCE=S3WNq^|_SE=; z>4_OwHEcnAg9i(C3;sKjy8`(5sNAeghEkczp{`4|o)u zKrNJ)g}fz5!D-WU5~E{b>(ZsL%K)|sCjBYU^b*R^M&vG^m+A+$Ro*8CyCPt_A*T=s zOTkIdbc+PvDG6)=u&4ZdlNqpDU|;g{jb*?fVC7pRC=3MxK77!}@G7xYz@7sve_g<~ zgA2EYg0czNOY)8{U|T?+jR$9)3ndcn#5u;m6bX0Yg=Q$s;ZLEVTYO*q3uO#2C$J-& zM`RZb?6sO&I{6$}Jl=7fqFP`+U|+;Te=?Wu0^}J{0W1TgL|pMC^EwOkU&D9$Q*e&5 z5FhBU%%y4w5f?6^6KBi8un2Z8XCANx1cgeh7T9&#^3kE69D!D zYQQ7Q=n#M?tY;Q4q;tKGfsQjU@Fx3{cu$559#I2!3MOv!!TS)f(=dO^x06y1o&$DM z%pyRPh6})cj|hmU5@7wnUZxuu{U~=u1@_rWzO4_K6IkPpZGWytNhwM+u=RN8Pf_CW z3b+kUA7~rchU`)Rt;UsybaSGjWB~grT}Td7QL^Y|q+RiY_N8l)Ye3 zGpg=QPfrK7A9jm=wlW(;V26M$z8?qwJIZo8e;4D2NA{tMaSg{koQqfLr) zPR+tXV5dRyl;+K*xGBmxaJ(-+r~`?qxaCt=VZ6XM-D^X9bHA!e!* zq{z}aIkU(YH5yn6?CDQYtj4(HR<<>dK+MZ@mL&Gr1jL9xWc&Na&m+Uv0M0ZZs; zUyg|+uoO`Lgqwu@?Mr}V0Q&>-mi`n=7Ty;kD(c&FQ3ox4VDI6f?m)2=!Q55fT!A}l zft3UM7|+9a6w3mlad8 zvg`%+1+u`OV%d-PMurJH1n6@{%ds2-cA~Ii5iYoe-AQ1_jsC@Q3hZ}3pto?#X<#|* ziwm6R0DTSV;hw>A0e065k%wE4VAl_93ha>IqJbqb)&(pc zoXh1c$-sQTR>1y8>@EfG%#!+*9hDEjj{Lh7qgX`=!qc*VO)XpAQC6M<%n$4~fODwN z(u#mNjlNYYhoAQw^+j6%Y$c@~Jc?F}_q5zaeDkTIH37SwuEV{%6?W~wpa(7RDIM4< zu=`snZse{DuLl5eU!ZLQ27AN}J%F|a*k^$`tfwTL!Ui|S!EOPdiNt5C16B(tMT5NU0WYv7V0Tjam7$-ow!>X1DD6=X^(%nwM+l$C6I1U6b_jND+-HDfJqGN)kS`~JMTW3bV3;j_ zNn;l{Ps8pJJlu|~=U_K_nHRVXb{BwE$-A3@^@BQJd{H|fz9bZz3hZv}Rk2O$1Lg$Q zjz`ce&lU~fd<(99hpE`&@ycHam4z1Mnau~h6j_y0^J?8O<6LTRGmNsm?pmx7rqf6kR&Iq_WrxtQOdH@`55@=1t?85sR3_7uEg zW>KvtECa7msErcK0#+WGf8X*N3eOL$A|kI+hEN1-3G)H4m*Z6zfeA!?2CxO7cN+bK zy%yNa0?h03kal|$I5R_b?XcT|2kSG~t%BXHA-gVMm{-6SfPE8Qw=ql@+$ht4Nq1X; z-EORU?At+qgQ8N2k=ecr*s7Wiy7e8{USMCwG=XN4ihV!a6;Y0Lkli6*<6uvJiv1Yg z*~SdleiEe4WnR$Gs_ds=e#hvS?5Baf!X-wzdk$EQQD5vAfc<1y2E_(;{lIFC8sboa zEi`&X+>nhkZwz;QV)-ddz{c0( zDqqH$fK348ia>wnXa_cNL~iTSwi;lofTh?9F!U4F1uRaSqL>2P1ng#`COftOn`G2v z$5#0FqfowVhuvgQKCGa)>;jgW+uY9cGu-M;b2zLk@hFb{c%?1z0w(MbUg->D1;%Hz zzXc3EFR+uan^#8VOq{3ieiRRne~!~2(MhD-7dXxVIxL>Rc!qho;{vc_da1}&#LqeZ zXnjU)rmwxD0vC@0A|DRAF_A2wFpvgu6c2pGgER=lPXF5r^Sdqb6eDQL0s9ReIa?&I z(|Vmi$iRM}j|c$q_Z1Sw(5lYS5A1D>I-I@;71$4r9E@-xMPs=q2M`eryUj*BiiiiP zV0Lk9O`R8ZKG^MsfAptBq~LwCQF|gXKuY4&l3f<;{tb_3L^&`&NVCWf*eel5pubqY ztbHNHETSCP9z2x4O2h(i7P2qz0ILO+);J&nYl3-?RT0lxHDK+)iWH16WE-&x?+1;3 zKB5a4MoPI3kJtok9Ul5qBDUcD4TdoxKO?pSdn}apUBGfN`;;N<1(t`0 z%1DXW5A#cmp@l~r0+i1frQsN$0%eQt?j*3sLw=qD_D!1a=AzC=oCdZ)d;#(ga7Ua2 z_Ak~5@g>wICE^0GdyN(z(T}KCM&|Mes9>*>5HI23xHz3K?+OZs12PK584YX}Sy4Ql z@i245Dj65257-W4#qLZ2_OC_=&J18rjzC+)eYc7;3)p9j8bUV(C^ewapW-aSs}>NC z56*HxThY(apWBt4ZE+Nuhm`Y?VUSn9|L0A)LbYfkM2W>jC9i(qAZl_B#L8Cv_ zC?kRNDPM>DOiINFLkm|g09OQ3i@RnOcA*HQR@s1ka_86%sa2krckTlB0sAWWsP-y? zQo9ovsK6+-FJgY(&vr;HZjhuJSq9t>3=&V4SSTEqCorIT*&T)cZ_Ue>Eh@lh&!ImF z`CMs31277G3%G;^xju(uGg=4xvWD%AG@u+b^!d6k=wGbpPw*pOc;f-`P^RILI$9VU%p*2dqF;}me}0^ znDR&NzQZs8E|pjHd)j4+zS}`Ddl+_mp zw<5~%IxO$T1GWIz_v_^Oeu}6Cwr|v%EcXL5up@{A-A*dgzO4nR`WKl#o#D?gT)^-%40nh9RCb!+lN^UZ;!&N<&ta(( z7(T-EJ;bl(T|wm<0kl| z3FiD{>6M5-&vcs6iZNpura7w^Bjb6PVDA%LrkAMZLt}#K^J#|T7(T{u3fGNqGMq~O zptmOeOcR~ct9*^=-OTd>!!MZN{U-ibnZA{HlvEB2l@ss`rn5gvU6@~y^=BLNJZYjI zF}Zz{-R@$Zw@vilGW>ITN2Fm z4c}D>K4;?Lx~KD8G|?}ypX^rU`X>EUjXDN@R9VNqL4K+kVYlj36P#s&Wu584SVZ|x z@!ZF-gi$!2~~If?qPhC~M}AK-|(z@BzKt ziGJJ!pEmJ)9EKIA37(?YXaW}fF5mVG<(STdL{d1Rhoh6Dc1Oy9#W_sP%{AUgNSGz$XU!#qbBewX1# z3D#u0-pue8raw)vRnjHg#dPMeJ`r|nm3}@d=Btk2e6h+BA6zBYSylwsi5Ij7gD;3z zwBx~7#9?c3@UZx)^@ZS1#XHvbg71j4wxr-$ao!dPo)-hQL%{(hBB*`Z4|AvJV0tiJ zq+^~n4XdU3qAvJI@DcGyaEtt575OAqSUVv>*cW`Ae&Y4uTfw))TfukeC*Bbsbw(@E zN`jKoIlXhbGF{15@;ci)+m-gt^_}aL^_@@BPkB<=)cLIPtg>C%(Ro<@l*7s~<@L@p zooAFYogXM4ba{sVRF68gYmz!io!T{Bo!)h?dT-Ze2Ob8^i| z%Sp>Sma}UixOVf} z&Dv&doA#`>L))e8S^GBqw70c)wD+`&>te03>oTpG>#EjOS*zA9Shv8sz#6bNSyxzB zS=X-HNI&aF>lW)1>-MbMW8Gujx9))Tfc5yg59H7Kf%U_6A6Y-Lerz4E*=&yWDeF^g zDYi_T-D$gvisG(u0vgi z)I;i#uJh)fdR`r{s1~;+-r}{STCz}oOHh07UDFZ%Svo8aTGm>gwCr5-g5?FvUaG(H zhg$r$*G_7e*14_jbt%Trnqp12PPJxPi`LazYu7bfo7b(fu3Gnm z@v}Z*-D>T&?m%6B)B5JRcdhTP`@s6adWZbk9JY8{()vtW=K2}78SAU$&sJq?v8`Oc z(YA5@7TXr8!Fnw|WP5LeCVzI#9?Nw&WkaF8a6`Ghe8YqCXMfPX-u{UF3H#O!+wI%! z`|Ky}r|cNT{@FiTb)tvRWIaw zUkrl(ANwH$oDYOKh!Zv3jTLN6Fdl2lB#|7P5S)N-y6IwCaE9>{GlV~w2Weqtut_wD z_TY-(3b8^w7<|I~L*3sN+$nYj_sXBx8{8-MiN4^G;1O{o_%Z#&$DNv@b;c|4imx-N zGf7EOGCDImGnGswyECsdPsvltI;)f_v-T{7Cso89?iZSL0Prm$%ESdR0j6Z@?NlsvN81Cuil% z$-jGcNnUlK$Ss{Sr?dn>4upCuMd9qbOUv^_aVZ;>rPXCP8)IH>rGQ`oIupV`%Zl?$ zL^EXjL?BBsr@Cymm|a<#3$b)=W$A2@Q(jI?%dqXUwL#Q~;*7FNQP(Q&UxW`@P`NFu z%qSKVLS?a_2vy!wmR~_J$|=vQE|`;3ahC)u!eB`loRwc$P@FTXLM)VAz$@m>t`rr^ zaiycG3C;yjP@bQkn_pav z*cRkgS5%gk=T{JvTUyL!UM|aC$SyxWueg-C02R&#w4k&kzdC=eSVd|PEl}aA6~tXy zP+gD<59ZA&0h8P^EAH!ffb)h53uU6`PC;o&W%X>t6wzfE8Osr_D$l8$T`J*wOmIbR zPI11@f-;#^sps-NW~8j3x=Qi@(i2-z2^!awDl@A;&tL_^tei6G3~)uh4uj(!>Cgy+ z@;f89FsEG1E}5TGTwRQsA|a{({+v8i1l=g+S~a_57SfCARZ>|>IbB^?y{x0IuD(IE zHZ&}&??9!Zx>eo5HX-Q#g^Oz1bs~geEsbQou;YQYdQn}Si?7Ns?k+@4sjjYodJT-S zc_n1z&7MWXDwr_lm6eslK&F!+-5dLf$fO-ZiDS2`U`}#W@v~T+N9Zs%{Io z66eg4JO!omO7f^CQB7Px)pQ}GxXZ*+=$kJS%b=ar(kd3VHUsWxZLb#%_y|^qZO*MN zqPb>S2dkwQyU}50qqbMNvrZ$<=5%Mg2D1*TtI!eJH zgwGT(D4}x!pbA+>a&CMh)-Tr!Q%Gl95IV07DZ)qO4)j$+?p+ICK1K>y#4nT9+P<*9 zyuJ=JK^AoL7HeoV)3;}W!uMYhX53Iku<7n7lNI+vL#;!kVRtVP*RZRRa_d_<+7Ti2 z*<>4XiL76^q`qz_&Mc-~!^m-pAGFs6Wj>!cAYmr6uOSNqGx=k8Z8WkFO18R5pR*Z-Nz6;zm`OKUk?m|N@s$nj zOPOm?J9FvAvbGjN+iIcq2NOD>Y^{?)A!3hOl~kV`nlAdWdN2{q^lIWkarO zZk$uI68eYrE5+iDrG+hZ&17;mz^tUs2PPB&YDC`Rn&#F<`Z`s-co_<3_|1Ce3+SLW zOLbA)j~9EkjN4QP)yfLlxzWb}=mGneH;CFLZO9Y2kwJ($gri5HerZXo=zyMFZR<*1 z$1KbpX54xe5 z3HmgC%(5y&PZX$1k2qvL3Q63sXBnkc!m6=uQyot zF;}Y*n-Q33x|fA`@FnpoJj->SpnC=L_-F^#Z&B&aC zZu)A3uWI4gdywYw>EmVkR)PbXl0!L04(E{HmoquGpEY881N2G}U*Jfsp6?Fn>uTkY z`*ZAT06FlWrW)y|6pDu|DyU1@4e|yb5%t_*+f8iS61KGx_vh{SsFN;z54tJVo$P-J zrOQsf#vqsVSjbQkR3%iB*C7Tv&ycVXiC=MqBs z@b^HG%q{6*Bc$t9alOIg-UuI_U>~A=>aDUB2i^b16j`U9WZ2_VGi81HcLOICIJl;- z*3dacFGgCuM1g)M0JtD^Yv5ZMM{T<$BR!pIhulvyt zW{a_fbDgQrT*mhd@i}bhM_0R_Me4uIVN5mp5#)KdLwID_pp`{Zy+(f`%YsY!ImC85 zed8gW2*6)q7-`G66*QvkFep=uy!t9rkXm8^$4&z$g+buY8#t5%3CDTU0OC1TU*$MF zPhrCbr(TC_b`G)OZ&sYgMhli-(kR!Bc*yT*g?c^s`bebH2Vq7erEj-GNPL>adnl(y z+K~Pi9pVO6xxuGWtKnF@$gzOWnMPQ9jCh6o`-aZD+u)sI@P6Ymiu@MHZ6@R%@$sPl z^g-~<@k4whew)O1$##Q0*~|V_%32cYdC|Kn3%C~K@w;&({K`Na^iuu0Ow|N+2-hNx z^+C7%Qi&eOCun4%@n;I>Pk7WsYY)0#3a2}Lrr9eCswxd|OgQ&m%VT3~Q((S*c+XmhHga$@gPhS!AjZeb+ zL+~N@w;4tY;97pbA))}uIMF;o_5yAi$AIB4AZn2A?}U5^x?eTH-(|fl8A>0#F+x1Z zaJao6GH|Mz@b7yDuJ<5^4P5U*zRx(-Wz2)v#Sw#|&ntrN*9^*3@`QXnYEY(Rus;Xg z{3TLkAcubt0ZZg+HTAJDT@zF>!Wd~t?1mhs@#`4-A!iytFv<#f1UK&2uA`Q7l;7Vs z`f<4VA-hRso~yVqar2ii*Hlg&N8Imfmbjp> zL=U<*fnSRn3Fxm5=R8VcMh1ib*lOf_Gs21P(=8HRG6Wt$caq$^&u}$*;`-^t{m<&-x zK8S6=FKY2)GN9fS&;+pm(#8O51JrN%@t6$tETF#~| z^}D^IQX3==F<-k8berZIlVNX>{)xHB0owmH7db>w0zsU@xp)(m@(+7e*p@ok`K~&m z&<}8}xC6he=!mFjZq8`@rwF2bPE!wOxQ`IKT9-(8?E5|>(MISr$<}x{>^CAGWQ>}k@4v;&uh_a{!zP*%@4I0HvgoJV)KF)%jSRT z=11CS#{VqcN7=P;ghx5FBsL?oo7i+}$!tbysol(Y zj5dYMQCbF@vAQ{0yM^&MEtAdbbv$0j$7r`QJwcnw<_)?zR=bVyL~Rv$`{QKc&J**;yIekT$N0#o zTgA5Ii&0a>Ja8qGHmR5fuD>LUsM|Epc$c(yjS=&bKP0Z?52B`tJCe^t-LBo0{BBg1 zXn?ysQKtKT5$@n4+!0qlxQY=5d;%A~9O}OR7wqp)4

^WgX=2-+Rb9p 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 literal 0 HcmV?d00001 diff --git a/bitset.h b/bitset.h new file mode 100644 index 0000000..fe1f692 --- /dev/null +++ b/bitset.h @@ -0,0 +1,26 @@ +// +// Created by Steven on 10-Dec-17. +// + +#ifndef BITSET_H +#define BITSET_H + +//Reference: https://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching + +#define EVAL_BOOL(n) ((n)!=0) + +#define BITS_SET(val, bits) (val) |= (bits) +#define BITS_RESET(val, bits) (val) &= ~(bits) +#define BITS_FLIP(val, bits) (val) ^= (bits) +#define BITS_TEST(val, bits) ((val) & (bits)) +#define BITS_TESTNOT(val, bits) (~(val) & (bits)) + +#define BITS_SET_BIT(val, bitpos) BITS_SET(val, 1 << (bitpos)) +#define BITS_RESET_BIT(val, bitpos) BITS_RESET(val, 1 << (bitpos)) +#define BITS_FLIP_BIT(val, bitpos) BITS_FLIP(val, 1 << (bitpos)) +#define BITS_TEST_BIT(val, bitpos) EVAL_BOOL(BITS_TEST(val, 1 << (bitpos))) +#define BITS_TESTNOT_BIT(val, bitpos) EVAL_BOOL(!BITS_TESTNOT(val, 1 << (bitpos))) +#define BITS_COND_BIT(val, bitpos, bit) do { if(bit) BITS_SET(val, 1 << (bitpos)); else BITS_RESET(val, 1 << (bitpos)); } while(0) + + +#endif //BITSET_H diff --git a/common.h b/common.h new file mode 100644 index 0000000..009bb51 --- /dev/null +++ b/common.h @@ -0,0 +1,35 @@ +#ifndef COMMON_H +#define COMMON_H + +typedef unsigned short ushort; +typedef unsigned long ulong; + +struct PSTRUCT { + Byte len; + Byte str[]; +}; + +#define ABS(x) ((x)>0?(x):-(x)) + +#define HIWORD(x) (((x) & 0xFFFF0000) >> 16) +#define LOWORD(x) ((x) & 0x0000FFFF) + +#define TO_PSTRUCT(x) (* (struct PSTRUCT *) (x)) + +#define PLEN(x) (TO_PSTRUCT(x).len) +#define PSTR(x) (TO_PSTRUCT(x).str) + +#ifndef M_PI +#define M_PI 3.1415926535897932 +#endif + +#define ITOC(n) ((n) + '0') +#define CTOI(n) ((n) - '0') + +#define ARRLEN(a) (sizeof(a)/sizeof(*(a))) + +#define L2PT(p,n) do { (p).v = HIWORD(n); (p).h = LOWORD(n); } while(0) +#define PT2L(p) (((long)(p).v << 16) | (long) (p).h) +#define TO_PTL(v,h) (((long)LOWORD(v) << 16) | (long) LOWORD(h)) + +#endif \ No newline at end of file diff --git a/freecell.c b/freecell.c new file mode 100644 index 0000000..596a5c1 --- /dev/null +++ b/freecell.c @@ -0,0 +1,265 @@ +#include "freecell.h" +#include "common.h" +#include + +#define FC_CEXPAND 2 + +static void SequenceDeck(Card *deck); +static void AddColumnCard(FCColumn **col, Card card); +static Card GetColumnCard(FCColumn *col); +static Card RemoveColumnCard(FCColumn *col); +static FCColumn *ExpandColumnSize(FCColumn *old, Size ptrSize); + +static Boolean AlternatingColours(Card a, Card b); + +void FreecellInit(FCState *state) { + memset(state, 0, sizeof(*state)); +} + +void FreecellStartGame(FCState *state, ushort seed) { + state->moves = 0; + state->seedno = seed; + state->cols = FreecellShuffle(seed, state->cols); + memset(state->store, 0, sizeof(state->store)); + memset(state->foundation, 0, sizeof(state->foundation)); +} + +FCColumn **FreecellShuffle(ushort seed, FCColumn **deal) { + ulong state = seed; + ushort i, j; + Card deck[CARD_QTY]; + + /* Allocate deal memory */ + if(!deal) { + deal = (FCColumn **) NewPtr(FC_COLS * sizeof(FCColumn *)); + if(!deal) return NULL; + for(i = 0; i < FC_COLS; ++i) { + deal[i] = (FCColumn *) NewPtr(SIZEOF_FCCOLUMN(FC_DCSIZE)); + if(!deal[i]) goto err_inner; + } + } + + SequenceDeck(deck); + + /* Freecell deal algorithm */ + for(i = 0; i < CARD_QTY - 1; ++i) { + ushort cidx; + Card toswap; + + state = (state * 214013L + 2531011L) & 0x7FFFFFFFL; + cidx = CARD_QTY - 1 - ((state >> 16) % (CARD_QTY - i)); + + toswap = deck[cidx]; + deck[cidx] = deck[i]; + deck[i] = toswap; + } + + for(i = 0; i < FC_COLS; ++i) { + for(j = 0; 8*j + i < CARD_QTY; ++j) { + deal[i]->cards[j] = deck[8*j + i]; + } + + deal[i]->qty = j; + } + + return deal; + +err_inner: + /* Inner deal array error */ + while(i--) { + DisposPtr(deal[i]); + } + DisposPtr(deal); + return NULL; +} + +void FreecellDisposeDeal(FCColumn **deal) { + ushort i; + + if(deal) { + for(i = 0; i < FC_COLS; ++i) { + DisposPtr(deal[i]); + } + + DisposPtr(deal); + } +} + +/* Legal move requirements: + 1: If to is a LastCard, then val(to) == NULL OR + val(to) - 1 == val(from) and to, from have alternating colours. + 2: If to is a storage, then val(to) == CARD_NULL + 3: If to is a foundation, then val(to) + 1 == val(from) OR + val(to) == NULL and val(from) == 1 and have the same suit. +*/ +Boolean FreecellLegalMove(FCState *state, FCZone from, FCZone to) { + Card cardFrom = GetCardAt(state, from); + Card cardTo = GetCardAt(state, to); + + if(CARD_EMPTY(cardFrom)) + return false; + + switch(to.zone) { + case FCZ_LASTCARD: + return CARD_EMPTY(cardTo) + || (CARD_GETNUM(cardTo) - 1 == CARD_GETNUM(cardFrom) + && AlternatingColours(cardTo, cardFrom)); + + case FCZ_COLUMN: + /* Can refer to empty columns as either */ + return CARD_EMPTY(cardTo) && CARD_GETNUM(cardFrom) == 13; + + case FCZ_STORAGE: + return CARD_EMPTY(cardTo); + + case FCZ_FOUNDATION: + return (CARD_EMPTY(cardTo) && CARD_GETNUM(cardFrom) == 1) + || (CARD_GETNUM(cardTo) + 1 == CARD_GETNUM(cardFrom) + && CARD_GETSUIT(cardTo) == CARD_GETSUIT(cardFrom)); + } + + return false; +} + +Boolean FreecellPlayMove(FCState *state, FCZone from, FCZone to) { + if(!FreecellLegalMove(state, from, to)) + return false; + + FreecellForceMove(state, from, to); + ++state->moves; + state->lastMove[0] = from; + state->lastMove[1] = to; + return true; +} + +void FreecellForceMove(FCState *state, FCZone from, FCZone to) { + Card moveCard; + + switch(from.zone) { + case FCZ_LASTCARD: + moveCard = RemoveColumnCard(state->cols[from.num]); + break; + + case FCZ_STORAGE: + moveCard = state->store[from.num]; + state->store[from.num] = CARD_NULL; + break; + + case FCZ_FOUNDATION: + moveCard = state->foundation[from.num]; + if(CARD_GETNUM(moveCard) == 1) { + state->foundation[from.num] = CARD_NULL; + } else { + ushort num = CARD_GETNUM(moveCard) - 1; + CARD_SETNUM(state->foundation[from.num], num); + } + break; + } + + switch(to.zone) { + case FCZ_LASTCARD: + AddColumnCard(&state->cols[to.num], moveCard); + break; + + case FCZ_STORAGE: + state->store[to.num] = moveCard; + break; + + case FCZ_FOUNDATION: + state->foundation[to.num] = moveCard; + break; + } +} + +void FreecellUndoMove(FCState *state) { + FCZone tmp; + + if(state->lastMove[0].zone == FCZ_NONE) + return; + + FreecellForceMove(state, state->lastMove[1], state->lastMove[0]); + --state->moves; + tmp = state->lastMove[0]; + state->lastMove[0] = state->lastMove[1]; + state->lastMove[1] = tmp; +} + +void SequenceDeck(Card *deck) { + ushort i; + deck[0] = TO_CARD(0, 3, 13); + for(i = 1; i < CARD_QTY; ++i) { + if(CARD_GETSUIT(deck[i-1]) == 0) + deck[i] = TO_CARD(0, 3, CARD_GETNUM(deck[i-1]) - 1); + else + deck[i] = deck[i-1] - TO_CARD(0, 1, 0); + } +} + +void AddColumnCard(FCColumn **col, Card card) { + FCColumn *workCol = *col; + Size ptrSize, reqSize; + + if(workCol->qty >= FC_DCSIZE) { + ptrSize = GetPtrSize(workCol); + reqSize = SIZEOF_FCCOLUMN(workCol->qty+1); + if(reqSize > ptrSize) { + *col = ExpandColumnSize(workCol, ptrSize); + workCol = *col; + } + } + + workCol->cards[workCol->qty] = card; + ++workCol->qty; +} + +static Card GetColumnCard(FCColumn *col) { + if(col->qty == 0) + return CARD_NULL; + + return col->cards[col->qty-1]; +} + +Card RemoveColumnCard(FCColumn *col) { + Card card; + + if(col->qty == 0) + return CARD_NULL; + + --col->qty; + card = col->cards[col->qty]; + col->cards[col->qty] = CARD_NULL; + return card; +} + +FCColumn *ExpandColumnSize(FCColumn *old, Size ptrSize) { + FCColumn *new = (FCColumn *) NewPtr(ptrSize + FC_CEXPAND * sizeof(Card)); + memcpy(new, old, ptrSize); + return new; +} + +Card GetCardAt(FCState *state, FCZone elem) { + switch(elem.zone) { + case FCZ_LASTCARD: + case FCZ_COLUMN: + return GetColumnCard(state->cols[elem.num]); + + case FCZ_STORAGE: + return state->store[elem.num]; + + case FCZ_FOUNDATION: + return state->foundation[elem.num]; + } + + return CARD_NULL; +} + +Boolean AlternatingColours(Card a, Card b) { + if(CARD_EMPTY(a) || CARD_EMPTY(b)) + return false; + + if(CARD_GETSUIT(a) == C_DIAMOND || CARD_GETSUIT(a) == C_HEART) { + return CARD_GETSUIT(b) == C_CLUB || CARD_GETSUIT(b) == C_SPADE; + } + + return CARD_GETSUIT(b) == C_DIAMOND || CARD_GETSUIT(b) == C_HEART; +} \ No newline at end of file diff --git a/freecell.h b/freecell.h new file mode 100644 index 0000000..65e4126 --- /dev/null +++ b/freecell.h @@ -0,0 +1,63 @@ +#ifndef FREECELL_H +#define FREECELL_H + +#include "common.h" +#include "gametypes.h" + +#define FC_STORES 4 +#define FC_COLS 8 +#define FC_DCSIZE 13 + +/* Seed values [1, 32001) */ +#define FC_SEEDLO 1 +#define FC_SEEDHI 32001 + +/* GameElem Structs */ +enum { + FCZ_NONE=0, + FCZ_LASTCARD, + FCZ_COLUMN, + FCZ_STORAGE, + FCZ_FOUNDATION +}; + +typedef struct { + Byte zone; + Byte num; +} FCZone; + +#define FCZONE_EQ(a, b) (\ + ((a).zone == (b).zone) \ + && ((a).num == (b).num) \ +) + +#define SIZEOF_FCCOLUMN(qty) (sizeof(FCColumn) + (qty) * sizeof(Card)) + +typedef struct { + ushort qty; + Card cards[]; +} FCColumn; + +typedef struct { + ushort moves; + ushort seedno; + FCColumn **cols; /* Size FC_COLS */ + Card store[FC_STORES]; + Card foundation[4]; + FCZone lastMove[2]; +} FCState; + +/* Game Setup */ +void FreecellInit(FCState *state); +void FreecellStartGame(FCState *state, ushort seed); +FCColumn **FreecellShuffle(ushort seed, FCColumn **deal); +void FreecellDisposeDeal(FCColumn **deal); + +/* Game Play */ +Card GetCardAt(FCState *state, FCZone elem); +Boolean FreecellLegalMove(FCState *state, FCZone from, FCZone to); +Boolean FreecellPlayMove(FCState *state, FCZone from, FCZone to); +void FreecellForceMove(FCState *state, FCZone from, FCZone to); +void FreecellUndoMove(FCState *state); + +#endif /* FREECELL_H */ \ No newline at end of file diff --git a/gameintf.c b/gameintf.c new file mode 100644 index 0000000..ba2e0cc --- /dev/null +++ b/gameintf.c @@ -0,0 +1,245 @@ +#include "gameintf.h" +#include "gamewind.h" +#include "gamewindlow.h" +#include "gamemenu.h" +#include "freecell.h" + +/* HandleGameClick, DragActionProc */ +static FCZone startHoverElem; +static FCZone lastHoverElem; +static Boolean lastHoverLegal; + +static pascal void DragActionProc(void); + +static short RndRange(short lower, short upper); + +FCClickErr HandleGameClick(FCState *state, Point hitPt) { + FCZone hitElem, dragElem; + Point dragPt; + long theLPoint; + Rect boundsRect = {-3, -3, 3, 3}; + Rect cardRect; + RgnHandle cardRgn; + FCClickErr theErr; + + hitElem = GetGamePtLoc(state, hitPt, &cardRect); + if(hitElem.zone == FCZ_NONE + || CARD_EMPTY(GetCardAt(state, hitElem))) { + theErr = FCCE_BADHIT; + goto err; + } + + /* Drag has slack to differentiate from double-click */ + dragPt = hitPt; + OffsetRect(&boundsRect, dragPt.h, dragPt.v); + while(PtInRect(dragPt, &boundsRect) && StillDown()) { + GetMouse(&dragPt); + } + + if(!StillDown()) { + theErr = FCCE_NODRAG; + goto err; + } + + /* Rect now starts in a different location */ + OffsetRect(&cardRect, dragPt.h - hitPt.h, dragPt.v - hitPt.v); + cardRgn = NewRgn(); + OpenRgn(); + FrameRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO); + CloseRgn(cardRgn); + + /* Create drag outline */ + boundsRect = FrontWindow()->portRect; + startHoverElem = hitElem; + lastHoverElem.zone = FCZ_NONE; + lastHoverLegal = false; + theLPoint = DragGrayRgn(cardRgn, dragPt, &boundsRect, + &boundsRect, noConstraint, &DragActionProc); + if(BAD_PTL(theLPoint)) { + theErr = FCCE_OOBDRAG; + goto err; + } + + dragPt.v += HIWORD(theLPoint); + dragPt.h += LOWORD(theLPoint); + dragElem = GetGamePtLoc(state, dragPt, &cardRect); + if(dragElem.zone == FCZ_NONE) { + theErr = FCCE_BADDRAG; + goto err; + } + + if(!lastHoverLegal) { + theErr = FCCE_BADMOVE; + goto err; + } + + InvertRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO); + /* Play move */ + if(!FreecellPlayMove(state, hitElem, dragElem)) { + theErr = FCCE_BADMOVE; + goto err; + } + + GameDrawMove(state, hitElem, dragElem); + return FCCE_OK; + +err: + /* In case other deinit needs to occur */ + return theErr; +} + +pascal void DragActionProc(void) { + Point mousePoint; + FCZone hoverElem; + Rect hoverRect, unhoverRect; + FCState *state; + + /* Invert spaces that are able to be placed */ + GetMouse(&mousePoint); + state = (FCState *) GetWRefCon(FrontWindow()); + hoverElem = GetGamePtLoc(state, mousePoint, &hoverRect); + if(FCZONE_EQ(hoverElem, lastHoverElem)) + return; + + if(lastHoverLegal) { + GetFCZoneRect(state, lastHoverElem, &unhoverRect); + InvertRoundRect(&unhoverRect, CARD_XRATIO, CARD_YRATIO); + } + + lastHoverElem = hoverElem; + lastHoverLegal = FreecellLegalMove(state, startHoverElem, hoverElem); + if(lastHoverLegal) { + InvertRoundRect(&hoverRect, CARD_XRATIO, CARD_YRATIO); + } +} + +void GameDrawMove(FCState *state, FCZone from, FCZone to) { + Card replaceCard = GetCardAt(state, from); + Card moveCard = GetCardAt(state, to); + + switch(from.zone) { + case FCZ_LASTCARD: + DrawRemovedColCard(state->cols[from.num], from.num); + break; + + case FCZ_STORAGE: + DrawStorageCard(replaceCard, from.num); + break; + + case FCZ_FOUNDATION: + DrawFoundationCard(replaceCard, from.num); + break; + } + + switch(to.zone) { + case FCZ_LASTCARD: + DrawLastColumnCard(state->cols[to.num], to.num); + break; + + case FCZ_STORAGE: + DrawStorageCard(moveCard, to.num); + break; + + case FCZ_FOUNDATION: + DrawFoundationCard(moveCard, to.num); + break; + } + + if(state->moves <= 1) { + MenuUndoState(true); + } +} + +void GameNewGame(FCState *state, ushort seed) { + Boolean needTitleUpdate; + if(!seed) { + seed = RndRange(FC_SEEDLO, FC_SEEDHI); + } + + needTitleUpdate = seed != state->seedno; + FreecellStartGame(state, seed); + if(needTitleUpdate) { + WindUpdateTitle(FrontWindow()); + } + MenuUndoState(false); + ForceRedraw(); +} + +/* To optimize */ +/* elemRect can be NULL if not required */ +FCZone GetGamePtLoc(FCState *state, Point pt, Rect *elemRect) { + FCZone res; + Rect boundsRect; + ushort i; + + for(i = 0; i < FC_STORES + 4; ++i) { + GetStoreRect(i, &boundsRect); + if(PtInRect(pt, &boundsRect)) { + res.zone = (i >= FC_STORES) ? FCZ_FOUNDATION : FCZ_STORAGE; + res.num = (i >= FC_STORES) ? i - FC_STORES : i; + if(elemRect) *elemRect = boundsRect; + return res; + } + } + + for(i = 0; i < FC_COLS; ++i) { + GetColumnRect(i, state->cols[i]->qty, &boundsRect); + if(PtInRect(pt, &boundsRect)) { + if(elemRect) *elemRect = boundsRect; + GetLastStackedRect(i, state->cols[i]->qty, &boundsRect); + if(PtInRect(pt, &boundsRect)) { + res.zone = FCZ_LASTCARD; + if(elemRect) *elemRect = boundsRect; + } else { + res.zone = FCZ_COLUMN; + } + + res.num = i; + return res; + } + } + + res.zone = FCZ_NONE; + if(elemRect) + SetRect(elemRect, 0, 0, 0, 0); + return res; +} + +void GetFCZoneRect(FCState *state, FCZone elem, Rect *elemRect) { + switch(elem.zone) { + case FCZ_LASTCARD: { + FCColumn *theColumn = state->cols[elem.num]; + GetLastStackedRect(elem.num, theColumn->qty, elemRect); + } break; + case FCZ_COLUMN: { + FCColumn *theColumn = state->cols[elem.num]; + GetColumnRect(elem.num, theColumn->qty, elemRect); + } break; + + case FCZ_STORAGE: + GetStoreRect(elem.num, elemRect); + break; + + case FCZ_FOUNDATION: + GetStoreRect(elem.num + FC_STORES, elemRect); + break; + + default: + SetRect(elemRect, 0, 0, 0, 0); + } +} + +/* If upper <= lower then result undefined. */ +short RndRange(short lower, short upper) { + asm { + CLR.W -(sp) ; d0 = Random() + _Random + CLR.L d0 + MOVE.W (sp)+, d0 + MOVE.W upper, d1 ; d1 = upper - lower + SUB.W lower, d1 + DIVU.W d1, d0 ; d0 = d0 % d1 + SWAP d0 + ADD.W lower, d0 + } +} \ No newline at end of file diff --git a/gameintf.h b/gameintf.h new file mode 100644 index 0000000..c64338b --- /dev/null +++ b/gameintf.h @@ -0,0 +1,26 @@ +#ifndef GAMEINTF_H +#define GAMEINTF_H + +#include "freecell.h" + +#define GAME_RANDOMSEED 0 + +typedef enum { + FCCE_OK=0, + FCCE_BADHIT, + FCCE_NODRAG, + FCCE_OOBDRAG, + FCCE_BADDRAG, + FCCE_BADMOVE +} FCClickErr; + +/* Mouse and Gameplay Handlers */ +FCClickErr HandleGameClick(FCState *state, Point hitPt); +void GameDrawMove(FCState *state, FCZone from, FCZone to); + +void GameNewGame(FCState *state, ushort seed); + +FCZone GetGamePtLoc(FCState *state, Point pt, Rect *elemRect); +void GetFCZoneRect(FCState *state, FCZone elem, Rect *elemRect); + +#endif /* GAMEINTF_H */ \ No newline at end of file diff --git a/gamemenu.c b/gamemenu.c new file mode 100644 index 0000000..37f7ce9 --- /dev/null +++ b/gamemenu.c @@ -0,0 +1,131 @@ +#include "gamemenu.h" +#include "common.h" +#include "gamewind.h" +#include "gamestate.h" +#include "gameintf.h" + +#define MBAR_ID 128 + +enum { + appleID=128, + fileID, + editID +}; + +enum { + apple_aboutID=1 +}; + +enum { + file_newID=1, + file_openID, + file_restartID, + file_quitID=5 +}; + +enum { + edit_undoID=1, + edit_cutID=3, + edit_copyID, + edit_pasteID, + edit_clearID +}; + +static void DoAppleMenu(short item); +static void DoFileMenu(short item); +static void DoEditMenu(short item); + +void MenuCreate(void) { + Handle mHandle; + mHandle = GetNewMBar(MBAR_ID); + SetMenuBar(mHandle); + DrawMenuBar(); + + mHandle = (Handle) GetMHandle(appleID); + AddResMenu((MenuHandle) mHandle, 'DRVR'); +} + +void MenuEvent(long menuitem) { + short menuID = HIWORD(menuitem); + short itemID = LOWORD(menuitem); + + switch(menuID) { + case appleID: DoAppleMenu(itemID); break; + case fileID: DoFileMenu(itemID); break; + case editID: DoEditMenu(itemID); break; + } +} + +void MenuEditState(Boolean active) { + MenuHandle theMenu = GetMHandle(editID); + if(active) { + EnableItem(theMenu, edit_cutID); + EnableItem(theMenu, edit_copyID); + EnableItem(theMenu, edit_pasteID); + EnableItem(theMenu, edit_clearID); + } else { + DisableItem(theMenu, edit_cutID); + DisableItem(theMenu, edit_copyID); + DisableItem(theMenu, edit_pasteID); + DisableItem(theMenu, edit_clearID); + } +} + +void MenuUndoState(Boolean active) { + MenuHandle theMenu = GetMHandle(editID); + if(active) { + EnableItem(theMenu, edit_undoID); + } else { + DisableItem(theMenu, edit_undoID); + } +} + +void DoAppleMenu(short item) { + GrafPtr *oldPort; + MenuHandle theMenu = GetMHandle(appleID); + + if(item > apple_aboutID) { + StringPtr name = (StringPtr) NewPtr(sizeof(Str255)); + GetPort(&oldPort); + GetItem(theMenu, item, name); + OpenDeskAcc(name); + SetPort(oldPort); + DisposPtr(name); + } else { + DlogAbout(); + } +} + +void DoFileMenu(short item) { + MenuHandle theMenu = GetMHandle(fileID); + switch(item) { + case file_newID: + GameNewGame(&gstate.fcGame, GAME_RANDOMSEED); + break; + + case file_openID: { + ushort openSeed = DlogOpenGame(); + if(openSeed != -1) { + GameNewGame(&gstate.fcGame, openSeed); + } + } break; + + case file_restartID: + GameNewGame(&gstate.fcGame, GAME_RANDOMSEED); + break; + + case file_quitID: + gstate.running = false; + break; + } +} + +void DoEditMenu(short item) { + switch(item) { + case edit_undoID: + FreecellUndoMove(&gstate.fcGame); + GameDrawMove(&gstate.fcGame, gstate.fcGame.lastMove[0], + gstate.fcGame.lastMove[1]); + break; + } +} \ No newline at end of file diff --git a/gamemenu.h b/gamemenu.h new file mode 100644 index 0000000..0615834 --- /dev/null +++ b/gamemenu.h @@ -0,0 +1,9 @@ +#ifndef GAMEMENU_H +#define GAMEMENU_H + +void MenuCreate(void); +void MenuEvent(long menuitem); +void MenuEditState(Boolean active); +void MenuUndoState(Boolean active); + +#endif /* GAMEMENU_H */ \ No newline at end of file diff --git a/gamestate.h b/gamestate.h new file mode 100644 index 0000000..def39a3 --- /dev/null +++ b/gamestate.h @@ -0,0 +1,13 @@ +#ifndef GAMESTATE_H +#define GAMESTATE_H + +#include "freecell.h" + +struct GlobalState { + FCState fcGame; + Boolean running; +}; + +extern struct GlobalState gstate; + +#endif /* GAMESTATE_H */ \ No newline at end of file diff --git a/gametypes.h b/gametypes.h new file mode 100644 index 0000000..4c8f98c --- /dev/null +++ b/gametypes.h @@ -0,0 +1,31 @@ +#ifndef GAMETYPES_H +#define GAMETYPES_H + +/* Card Type: ffssnnnn. High order can be used for flags. */ +#define CARD_FMASK 0xC0 +#define CARD_SMASK 0x30 +#define CARD_NMASK 0x0F + +#define CARD_GETFLAGS(c) ((Byte) ((c & CARD_FMASK) >> 6)) +#define CARD_GETSUIT(c) ((Suit) ((c & CARD_SMASK) >> 4)) +#define CARD_GETNUM(c) ((short) (c & CARD_NMASK)) +#define CARD_SETFLAGS(c, f) ((c) = ((c & ~CARD_FMASK) | ((f) << 6))) +#define CARD_SETSUIT(c, s) ((c) = ((c & ~CARD_SMASK) | ((s) << 4))) +#define CARD_SETNUM(c, n) ((c) = ((c & ~CARD_NMASK) | (n))) +#define TO_CARD(f, s, n) ((Card) (((f) << 6) | ((s) << 4) | (n))) + +#define CARD_NULL ((Card) 0) +#define CARD_EMPTY(c) (CARD_GETNUM(c) == 0) +#define CARD_QTY 52 + +typedef Byte Card; + +typedef enum Suit { + C_CLUB=0, + C_DIAMOND, + C_HEART, + C_SPADE +} Suit; + + +#endif /* GAMETYPES_H */ \ No newline at end of file diff --git a/gamewind.c b/gamewind.c new file mode 100644 index 0000000..f69452c --- /dev/null +++ b/gamewind.c @@ -0,0 +1,344 @@ +#include "gamewind.h" +#include "gamewindlow.h" +#include "common.h" +#include "pstring.h" +#include "strntol.h" +#include +#include + +#define TITLE_PREF "\pFreeCell - #" +#define DLOG_OPEN 128 +#define DLOG_OPEN_INPUT 3 +#define DLOG_ABOUT 129 + +typedef struct { + short dlgMaxIndex; + Handle itmHndl; + Rect itmRect; + short itmType; + Byte itmData[]; +} DialogItemList; + +static pascal Boolean DigitInputFilter(DialogPtr theDialog, + EventRecord *theEvent, short *itemHit); +static pascal Boolean AboutFilter(DialogPtr theDialog, + EventRecord *theEvent, int *itemHit); + +WindowPtr WindCreateTestEnv(Rect *bounds, StringPtr title) { + WindowPtr theWind; + Rect windRect; + + title = title ? title : "\pTest Area"; + theWind = NewWindow(0L, bounds, title, true, + noGrowDocProc, (WindowPtr) -1L, true, 0); + + return theWind; +} + +WindowPtr WindCreateGame(FCState *state) { + WindowPtr theWind; + Rect boundsRect; + Rect windRect; + StringPtr titleString; + unsigned char gameNumBuf[6]; + Pattern bkpat; + /* + titleString = (StringPtr) NewPtr(sizeof(Str255)); + if(!titleString) return NULL; + memcpy(titleString, TITLE_PREF, sizeof(TITLE_PREF)); + NumToString(state->seedno, gameNumBuf); + strcat_p(titleString, gameNumBuf);*/ + + SetRect(&windRect, 0, 0, WIND_XLENGTH, WIND_YLENGTH); + boundsRect = screenBits.bounds; + boundsRect.top += 20; + CentreRect(&windRect, &boundsRect); + theWind = NewWindow(0L, &windRect, "\p", true, noGrowDocProc, + (WindowPtr) -1L, true, 0); + /*DisposPtr(titleString);*/ + if(theWind) { + SetPort(theWind); + GetIndPattern(&bkpat, sysPatListID, 21); + BackPat(bkpat); + SetWRefCon(theWind, (long) state); + } + return theWind; +} + +void WindUpdateTitle(WindowPtr theWind) { + StringPtr titleString; + unsigned char gameNumBuf[6]; + FCState *state = (FCState *) GetWRefCon(theWind); + + titleString = (StringPtr) NewPtr(sizeof(Str255)); + if(!titleString) return; + memcpy(titleString, TITLE_PREF, sizeof(TITLE_PREF)); + NumToString(state->seedno, gameNumBuf); + strcat_p(titleString, gameNumBuf); + SetWTitle(theWind, titleString); + DisposPtr(titleString); +} + +ushort DlogOpenGame(void) { + DialogPtr theDialog; + Rect dlogRect, boundsRect; + short itemNo; + Handle textboxHandle; + volatile Size textboxHndlSize; + long inputText; + + theDialog = GetNewDialog(DLOG_OPEN, NULL, (WindowPtr) -1); + if(!theDialog) return -1; + dlogRect = theDialog->portRect; + boundsRect = screenBits.bounds; + boundsRect.top += 20; + CentreRect(&dlogRect, &boundsRect); + MoveWindow(theDialog, dlogRect.left, dlogRect.top, true); + ShowWindow(theDialog); + + do { + do { + ModalDialog(&DigitInputFilter, &itemNo); + if(itemNo == cancel) { + inputText = -1; + goto err; + } + } while(itemNo != ok); + + GetDItem(theDialog, DLOG_OPEN_INPUT, &itemNo, + &textboxHandle, &boundsRect); + textboxHndlSize = GetHandleSize(textboxHandle); + /* Ensure whitespace before / after string */ + HLock(textboxHandle); + inputText = strntol(*textboxHandle, textboxHndlSize, NULL, 10); + HUnlock(textboxHandle); + if(inputText < 1 || inputText > 32000) { + SysBeep(1); + } else { + break; + } + } while(true); + +err: + DisposDialog(theDialog); + return (ushort) inputText; +} + +pascal Boolean DigitInputFilter(DialogPtr theDialog, EventRecord *theEvent, + short *itemHit) { + char theChar; + + if(theEvent->what == keyDown || theEvent->what == autoKey) { + theChar = theEvent->message & 0xFF; + if(theChar == '\r' || theChar == '\x03') { + *itemHit = ok; + return true; + } else if(!isdigit(theChar) && theChar != '\b') { + SysBeep(1); + theEvent->what = nullEvent; + } + } + return false; +} + +void DlogAbout(void) { + DialogPtr theDialog; + Rect dlogRect; + Rect boundsRect; + short itemNo; + + theDialog = GetNewDialog(DLOG_ABOUT, NULL, (WindowPtr) -1); + if(!theDialog) return; + dlogRect = theDialog->portRect; + boundsRect = screenBits.bounds; + boundsRect.top += 20; + boundsRect.bottom /= 2; + CentreRect(&dlogRect, &boundsRect); + MoveWindow(theDialog, dlogRect.left, dlogRect.top, true); + ShowWindow(theDialog); + ModalDialog(&AboutFilter, &itemNo); + DisposDialog(theDialog); +} + +pascal Boolean AboutFilter(DialogPtr theDialog, EventRecord *theEvent, + int *itemHit) { + int windowCode; + WindowPtr theWindow; + + if(theEvent->what != mouseDown) + return false; + windowCode = FindWindow(theEvent->where, &theWindow); + if(windowCode != inContent || theWindow != theDialog) + return false; + + return true; +} + +void DrawGameInit(void) { + Point drawPoint; + ushort i; + + drawPoint.v = CARD_BD_Y; + drawPoint.h = CARD_BD_X; + for(i = 0; i < FC_STORES; ++i) { + DrawEmptyFrame(drawPoint); + drawPoint.h += CARD_ST_X + CARD_XLENGTH; + } + + drawPoint.h = WIND_XLENGTH - CARD_BD_X - CARD_XLENGTH; + for(i = 0; i < 4; ++i) { + DrawEmptyFrame(drawPoint); + drawPoint.h -= CARD_ST_X + CARD_XLENGTH; + } +} + +void ForceRedraw(void) { + GrafPtr thePort; + GetPort(&thePort); + InvalRect(&thePort->portRect); +} + +void DrawAll(FCState *state) { + DrawClear(); + DrawStorage(state->store); + DrawFoundation(state->foundation); + DrawPlayfield(state->cols); +} + +void DrawClear(void) { + GrafPtr thePort; + Rect *clearRect; + GetPort(&thePort); + clearRect = &thePort->portRect; + EraseRect(clearRect); +} + +void DrawPlayfield(FCColumn **cols) { + Point drawPoint; + ushort i; + + drawPoint.v = CARD_BD_Y + CARD_YLENGTH + CARD_ST_SEP; + drawPoint.h = CARD_BD_X; + for(i = 0; i < FC_COLS; ++i) { + DrawStack(cols[i]->cards, drawPoint, cols[i]->qty); + drawPoint.h += CARD_XLENGTH + CARD_PF_X; + } +} + +void DrawStorage(Card *cards) { + Point drawPoint; + ushort i; + + drawPoint.v = CARD_BD_Y; + drawPoint.h = CARD_BD_X; + for(i = 0; i < FC_STORES; ++i) { + if(!CARD_EMPTY(cards[i])) { + DrawCard(cards[i], drawPoint); + } else { + DrawEmptyFrame(drawPoint); + } + drawPoint.h += CARD_ST_X + CARD_XLENGTH; + } +} + +void DrawFoundation(Card *cards) { + Point drawPoint; + ushort i; + + drawPoint.v = CARD_BD_Y; + drawPoint.h = WIND_XLENGTH - CARD_BD_X - CARD_XLENGTH; + for(i = 0; i < 4; ++i) { + if(!CARD_EMPTY(cards[3-i])) { + DrawCard(cards[3-i], drawPoint); + } else { + DrawEmptyFrame(drawPoint); + } + drawPoint.h -= CARD_ST_X + CARD_XLENGTH; + } +} + +void DrawStorageCard(Card card, ushort pos) { + Point drawPoint; + + drawPoint = GetStorePt(pos); + if(!CARD_EMPTY(card)) { + DrawCard(card, drawPoint); + } else { + DrawEmptyFrame(drawPoint); + } +} + +void DrawFoundationCard(Card card, ushort pos) { + Point drawPoint; + + drawPoint = GetStorePt(FC_STORES + pos); + if(!CARD_EMPTY(card)) { + DrawCard(card, drawPoint); + } else { + DrawEmptyFrame(drawPoint); + } +} + +void DrawLastColumnCard(FCColumn *col, ushort colno) { + Point drawPoint; + + drawPoint = GetColumnPt(colno); + DrawStackedCard(col->cards[col->qty-1], drawPoint, col->qty-1); +} + +void DrawRemovedColCard(FCColumn *col, ushort colno) { + Point drawPoint; + + drawPoint = GetColumnPt(colno); + DrawStackedCard(CARD_NULL, drawPoint, col->qty); + + if(col->qty != 0) { + DrawStackedCard(col->cards[col->qty-1], drawPoint, col->qty-1); + } else { + DrawEmptyFrame(drawPoint); + } +} + +void CentreRect(Rect *toCentre, const Rect *bounds) { + short bdx, bdy, cdx, cdy; + + bdx = bounds->right - bounds->left; + bdy = bounds->bottom - bounds->top; + cdx = toCentre->right - toCentre->left; + cdy = toCentre->bottom - toCentre->top; + if(cdx > bdx || cdy > bdy) return; + + toCentre->top = bounds->top + bdy/2 - cdy/2; + toCentre->left = bounds->left + bdx/2 - cdx/2; + toCentre->bottom = toCentre->top + cdy; + toCentre->right = toCentre->left + cdx; +} + +/* + switch(elem.zone) { + case FCZ_LASTCARD: { + FCColumn *theColumn = state->cols[elem.num]; + if(CARD_EMPTY(theColumn->cards[theColumn->qty-1])) + goto err; + + GetLastStackedRect(elem.num, theColumn->qty, &drawRect); + } break; + + case FCZ_STORAGE: + if(CARD_EMPTY(state->store[elem.num])) + goto err; + + GetStoreRect(elem.num, &drawRect); + break; + + case FCZ_FOUNDATION: + if(CARD_EMPTY(state->foundation[elem.num])) + goto err; + + GetStoreRect(elem.num + FC_STORES, &drawRect); + break; + + default: + goto err; + } + */ \ No newline at end of file diff --git a/gamewind.h b/gamewind.h new file mode 100644 index 0000000..798b941 --- /dev/null +++ b/gamewind.h @@ -0,0 +1,33 @@ +#ifndef GAMEWIND_H +#define GAMEWIND_H + +#include "common.h" +#include "gametypes.h" +#include "freecell.h" + +/* Window / Dialog creation */ +WindowPtr WindCreateTestEnv(Rect *bounds, StringPtr title); +WindowPtr WindCreateGame(FCState *state); +void WindUpdateTitle(WindowPtr theWind); + +ushort DlogOpenGame(void); +void DlogAbout(void); + +/* High-Level Drawing */ +void DrawGameInit(void); +void ForceRedraw(void); +void DrawAll(FCState *state); +void DrawClear(void); +void DrawPlayfield(FCColumn **cols); +void DrawStorage(Card *cards); +void DrawFoundation(Card *cards); + +void DrawStorageCard(Card card, ushort pos); +void DrawFoundationCard(Card card, ushort pos); +void DrawLastColumnCard(FCColumn *col, ushort colno); +void DrawRemovedColCard(FCColumn *col, ushort colno); + +/* Misc */ +void CentreRect(Rect *toCentre, const Rect *bounds); + +#endif /* GAMEWIND_H */ \ No newline at end of file diff --git a/gamewindlow.c b/gamewindlow.c new file mode 100644 index 0000000..740d1bc --- /dev/null +++ b/gamewindlow.c @@ -0,0 +1,156 @@ +#include "gamewindlow.h" + +void DrawCard(Card card, Point loc) { + Rect cardRect; + static short lineAscent = -1; + static short lineSpacing = -1; + + SetRect(&cardRect, 0, 0, CARD_XLENGTH, CARD_YLENGTH); + OffsetRect(&cardRect, loc.h, loc.v); + EraseRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO); + if(CARD_EMPTY(card)) return; + + TextFont(CARDFONT_ID); + TextSize(9); + if(lineSpacing == -1) { + FontInfo cardFontInfo; + GetFontInfo(&cardFontInfo); + lineAscent = cardFontInfo.ascent; + lineSpacing = cardFontInfo.ascent + + cardFontInfo.descent + + cardFontInfo.leading; + } + + FillRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO, white); + FrameRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO); + InsetRect(&cardRect, (CARD_XLENGTH * 9) / 40, + (CARD_YLENGTH * 9) / 40); + FillRoundRect(&cardRect, CARD_XRATIO, CARD_YRATIO, gray); + + loc.h += (CARD_XLENGTH * 2) / 40; + loc.v += (CARD_YLENGTH * 2) / 40 + lineAscent; + MoveTo(loc.h, loc.v); + DrawChar(ITOC(CARD_GETNUM(card))); + /*MoveTo(loc.h, loc.v + lineSpacing);*/ + DrawChar(SUIT2CHAR(CARD_GETSUIT(card))); +} + +void DrawStack(Card cards[], Point loc, ushort qty) { + ushort i; + + if(qty == 0) { + DrawEmptyFrame(loc); + return; + } + + for(i = 0; i < qty; ++i) { + DrawCard(cards[i], loc); + loc.v += CARD_PF_Y; + } +} + +void DrawStackedCard(Card card, Point loc, ushort pos) { + loc.v += CARD_PF_Y * pos; + DrawCard(card, loc); +} + +void DrawEmptyFrame(Point loc) { + PenState ps; + Rect frameRect = {0, 0, CARD_YLENGTH, CARD_XLENGTH}; + + GetPenState(&ps); + PenPat(black); + PenSize(1,1); + OffsetRect(&frameRect, loc.h, loc.v); + FillRoundRect(&frameRect, CARD_XRATIO, CARD_YRATIO, white); + FrameRoundRect(&frameRect, CARD_XRATIO, CARD_YRATIO); + SetPenState(&ps); +} + +/* Returns (INVAL_LOC, INVAL_LOC) on error */ +Point GetStorePt(ushort store) { + Point res; + if(store >= FC_STORES + 4) { + L2PT(res, INVAL_PTL); + return res; + } + + if(store >= FC_STORES) { + /* Set count from 4-7 to 1-4 backwards */ + /*store = (4 - 1) - (store - FC_STORES) + 1;*/ + store = (FC_STORES + 4) - store; + res.h = WIND_XLENGTH - CARD_BD_X + CARD_ST_X /* Correction Term */ + - store * (CARD_XLENGTH + CARD_ST_X); + } else { + res.h = CARD_BD_X + store * (CARD_XLENGTH + CARD_ST_X); + } + + res.v = CARD_BD_Y; + return res; +} + +Point GetColumnPt(ushort col) { + Point res; + if(col >= FC_COLS) { + L2PT(res, INVAL_PTL); + return res; + } + + res.v = CARD_BD_Y + CARD_YLENGTH + CARD_ST_SEP; + res.h = CARD_BD_X + col * (CARD_XLENGTH + CARD_PF_X); + return res; +} + +void GetStoreRect(ushort store, Rect *res) { + topLeft(*res) = GetStorePt(store); + if(BAD_PT(topLeft(*res))) { + SetRect(res, 0, 0, 0, 0); + return; + } + + res->bottom = res->top + CARD_YLENGTH; + res->right = res->left + CARD_XLENGTH; +} + +void GetColumnRect(ushort col, ushort csize, Rect *res) { + topLeft(*res) = GetColumnPt(col); + if(BAD_PT(topLeft(*res))) { + SetRect(res, 0, 0, 0, 0); + return; + } + + if(csize == 0) csize = 1; + res->bottom = res->top + (csize - 1) * CARD_PF_Y + CARD_YLENGTH; + res->right = res->left + CARD_XLENGTH; +} + +/* 0 treated as 1 so that cards can be played on empty columns */ +void GetStackedCardRect(ushort col, ushort csize, ushort pos, Rect *res) { + if(csize == 0) csize = 1; + + if(pos >= csize) + goto err; + + topLeft(*res) = GetColumnPt(col); + if(BAD_PT(topLeft(*res))) + goto err; + + res->top += pos * CARD_PF_Y; + res->right = res->left + CARD_XLENGTH; + + /* If card is last in column, use full area. Otherwise whats seen. */ + if(pos == csize - 1) { + res->bottom = res->top + CARD_YLENGTH; + } else { + res->bottom = res->top + CARD_PF_Y; + } + + return; +err: + SetRect(res, 0, 0, 0, 0); +} + +void GetLastStackedRect(ushort col, ushort csize, Rect *res) { + if(csize < 1) csize = 1; + GetStackedCardRect(col, csize, csize-1, res); +} \ No newline at end of file diff --git a/gamewindlow.h b/gamewindlow.h new file mode 100644 index 0000000..47d71e4 --- /dev/null +++ b/gamewindlow.h @@ -0,0 +1,77 @@ +#ifndef GAMEWINDLOW_H +#define GAMEWINDLOW_H + +#include "common.h" +#include "freecell.h" + +/* Game Dimensions */ + +#define CARDFONT_ID 25 +#define CARDFONT_PT 12 + +#define CARD_XRATIO 5 +#define CARD_YRATIO 7 +#define CARD_SCALE 9 + +#define CARD_XLENGTH (CARD_XRATIO * CARD_SCALE) +#define CARD_YLENGTH (CARD_YRATIO * CARD_SCALE) + +/* + ^ + |BD_Y + | +BD_Xv ST_X ST_GAP +<-->+--+<---->+--+ ... <------> + | | | | + | | | | + +--+ +--+ + ^ + ST| + SEP| XLEN + v PF_X <--> + +--+<----->+--+^ +PF_Y| | | ||YLEN + +--+ | || + | | +--+v + ... +*/ +/* Set to a ratio ? */ +#define CARD_PF_X 10 +#define CARD_PF_Y 12 + +#define CARD_ST_X ((WIND_XLENGTH - (2*CARD_BD_X) - (8*CARD_XLENGTH) \ + - CARD_ST_GAP) / 6) +#define CARD_ST_GAP 40 +#define CARD_ST_SEP (2*CARD_BD_Y) + +#define CARD_BD_X 10 +#define CARD_BD_Y 10 + +#define WIND_XLENGTH ((2*CARD_BD_X)+(8*CARD_XLENGTH)+(7*CARD_PF_X)) +#define WIND_YLENGTH \ + (((long) (screenBits.bounds.bottom-20) * WIND_XLENGTH) / screenBits.bounds.right) + +#define SUIT2CHAR(s) ('0' - 1 - (s)) + +#define INVAL_LOC (0x8000) +#define INVAL_PTL (0x80008000L) +#define BAD_PT(p) (((p).h == INVAL_LOC) && ((p).v == INVAL_LOC)) +/*#define BAD_PT(p) BAD_PTL(PT2L(p))*/ +#define BAD_PTL(p) (p == INVAL_PTL) + +/* Low-Level Drawing */ +void DrawCard(Card card, Point loc); +void DrawStackedCard(Card card, Point loc, ushort pos); +void DrawStack(Card cards[], Point loc, ushort qty); +void DrawEmptyFrame(Point loc); + +/* Low-Level Point Access */ +Point GetStorePt(ushort store); +Point GetColumnPt(ushort col); +void GetStoreRect(ushort store, Rect *res); +void GetColumnRect(ushort col, ushort csize, Rect *res); +void GetStackedCardRect(ushort col, ushort csize, ushort pos, Rect *res); +void GetLastStackedRect(ushort col, ushort csize, Rect *res); + + +#endif /* GAMEWINDLOW_H */ \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..856f33d --- /dev/null +++ b/main.c @@ -0,0 +1,144 @@ +/* TODO + - Bug where starting a new game causes some cards to not be drawn properly + and may leave residual cards in foundation / storage + - Optimize DlogOpenGame string to int +*/ + +#include "gamewind.h" +#include "gameintf.h" +#include "gamemenu.h" +#include "gamestate.h" +#include "freecell.h" + +struct GlobalState gstate = {0}; +static Rect dragRect; + +static void InitMacintosh(void); +static void InitGameState(void); +static void HandleEvent(short eventMask); +static void HandleMouseDown(EventRecord *theEvent); +static void HandleContentClick(Point mousePt); +short RndRange(short lower, short upper); + +void InitMacintosh(void) { + MaxApplZone(); + + InitGraf(&thePort); + InitFonts(); + InitWindows(); + InitMenus(); + TEInit(); + InitDialogs(0L); + randSeed = RndSeed; + + FlushEvents(everyEvent, 0); + InitCursor(); +} + +void InitGameState(void) { + FreecellInit(&gstate.fcGame); + gstate.running = true; +} + +void HandleEvent(short eventMask) { + int res; + EventRecord theEvent; + + HiliteMenu(0); + SystemTask(); /* Handle desk accessories */ + + if (!GetNextEvent(eventMask, &theEvent)) return; + switch (theEvent.what) { + case mouseDown: + HandleMouseDown(&theEvent); + break; + + case keyDown: + if(theEvent.modifiers & cmdKey) { + MenuEvent(MenuKey(theEvent.message & 0xFF)); + } + + /* FALLTHROUGH */ + case autoKey: + break; + + case activateEvt: + if(theEvent.modifiers & activeFlag) { + MenuEditState(false); + } else { + MenuEditState(true); + } + break; + + case updateEvt: + BeginUpdate((WindowPtr) theEvent.message); + /*EraseRect(&((WindowPtr) theEvent.message)->portRect);*/ + DrawAll(&gstate.fcGame); + EndUpdate((WindowPtr) theEvent.message); + break; + } +} + +void HandleMouseDown(EventRecord *theEvent) { + WindowPtr theWindow; + short windowCode = FindWindow(theEvent->where, &theWindow); + + switch(windowCode) { + case inSysWindow: + SystemClick (theEvent, theWindow); + break; + + case inDrag: + DragWindow(theWindow, theEvent->where, &dragRect); + break; + + case inMenuBar: + MenuEvent(MenuSelect(theEvent->where)); + break; + + case inContent: + if(theWindow != FrontWindow()) { + SelectWindow(theWindow); + } else { + GlobalToLocal(&theEvent->where); + switch(HandleGameClick(&gstate.fcGame, theEvent->where)) { + /*case FCCE_BADDRAG:*/ + case FCCE_BADMOVE: + SysBeep(1); + break; + + default: + ; + } + } + break; + + case inGoAway: + if(TrackGoAway(theWindow, theEvent->where)) { + HideWindow(theWindow); + gstate.running = false; + } + break; + } +} + +int main(void) { + Point cardPoint = {10, 10}; + WindowPtr testWind; + + InitMacintosh(); + InitGameState(); + MenuCreate(); + + SetRect(&dragRect, 4, 24, screenBits.bounds.right-4, + screenBits.bounds.bottom-4); + + testWind = WindCreateGame(&gstate.fcGame); + GameNewGame(&gstate.fcGame, GAME_RANDOMSEED); + + while(gstate.running) { + HandleEvent(everyEvent); + } + + FreecellDisposeDeal(gstate.fcGame.cols); +} \ No newline at end of file diff --git a/pstring.c b/pstring.c new file mode 100644 index 0000000..fb10361 --- /dev/null +++ b/pstring.c @@ -0,0 +1,22 @@ +/* Parameter List and Prototypes disabled so that stack frame not created */ + +#pragma options(!require_protos) +StringPtr strcat_p(/*StringPtr s1, const StringPtr s2*/) { + asm { + MOVEA.L 4(sp), a0 ; A0 = s1 + MOVEA.L 8(sp), a1 ; A1 = s2 + CLR.L d0 + CLR.L d1 + MOVE.B (a0), d0 ; D0 = n(s1) + MOVE.B (a1)+, d1 ; D1 = n(s2) + ADD.B d1, (a0)+ ; Update n(s1) + ADDA.L d0, a0 ; Offset s1 + TST.B d1 + BRA.S @2 +@1 MOVE.B (a1)+, (a0)+ + SUBQ.B #1, d1 +@2 BNE.S @1 + + MOVE.L 4(sp), d0 + } +} \ No newline at end of file diff --git a/pstring.h b/pstring.h new file mode 100644 index 0000000..3efcc1c --- /dev/null +++ b/pstring.h @@ -0,0 +1,6 @@ +#ifndef PSTRING_H +#define PSTRING_H + +StringPtr strcat_p(StringPtr a, const StringPtr b); + +#endif /* PSTRING_H */ \ No newline at end of file diff --git a/strntol.c b/strntol.c new file mode 100644 index 0000000..5029c41 --- /dev/null +++ b/strntol.c @@ -0,0 +1,90 @@ +/* + * Adapted from GCC strtol.c + */ + +#include "strntol.h" +#include +#include + +long strntol(const char *nptr, size_t sz, char **endptr, int base) { + const char *s = nptr; + unsigned long acc = 0; + int c; + unsigned long cutoff; + int neg = 0, any = 0, cutlim; + + ++sz; /* Bounds fix */ + + /* Skip whitespace, pick up +/- sign, detect prefix. */ + do { + c = *s++; + --sz; + } while(isspace(c) && sz > 0); + if(sz == 0) { + goto err; + } + + if(c == '-') { + neg = 1; + c = *s++; + sz--; + } else if(c == '+') { + c = *s++; + } + + + if((base == 0 || base == 16) && + sz >= 2 && c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + sz -= 2; + base = 16; + } + + if(base == 0) { + base = c == '0' ? 8 : 10; + } + + /* Compute cutoff between legal / illegal numbers */ + cutoff = neg ? -(unsigned long) LONG_MIN : LONG_MAX; + cutlim = cutoff % (unsigned long) base; + cutoff /= (unsigned long) base; + for(;; --sz, c = *s++) { + if(sz == 0) { + break; + } + + if(isdigit(c)) { + c -= '0'; + } else if(isalpha(c)) { + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + } else { + break; + } + + if(c >= base) { + break; + } + + if(any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) { + any = -1; + } else { + any = 1; + acc *= base; + acc += c; + } + } + + if(any < 0) { + acc = neg ? LONG_MIN : LONG_MAX; + } else if(neg) { + acc = -acc; + } + +err: + if(endptr != 0) { + *endptr = (char *) (any ? s - 1 : nptr); + } + + return acc; +} \ No newline at end of file diff --git a/strntol.h b/strntol.h new file mode 100644 index 0000000..5facf5b --- /dev/null +++ b/strntol.h @@ -0,0 +1,8 @@ +#ifndef STRNTOL_H +#define STRNTOL_H + +#include + +long strntol(const char *nptr, size_t sz, char **endptr, int base); + +#endif /* STRNTOL_H */ \ No newline at end of file