From 905565e7c402075ab169b17ac751bd59456beb85 Mon Sep 17 00:00:00 2001 From: hujun Date: Fri, 20 Mar 2026 15:54:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=95=99=E5=AD=A6):=20=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E6=95=99=E5=AD=A6=E5=BB=BA=E8=AE=AE=E9=93=BE=E8=B7=AF=E4=BB=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=80=99=E9=80=89=E7=89=8C=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 扩展教学建议链路,在 PrivateTeachingMessage 中增加 candidates 字段,支持前端展示候选牌、评分和原因标签。同时优化前端组件结构,抽离共享类型和工具函数,为后续页面拆分做准备。 - 后端:在 GameSessionService 和 GameMessagePublisher 中透传候选牌列表 - 前端:新增 GameMessageStack 组件展示教学候选,优化手牌区推荐牌高亮 - 测试:补充 GameMessagePublisherTest 验证候选牌消息结构 - 文档:更新 DEVELOPMENT_PLAN 和 H5_GAME_PAGE_ARCHITECTURE 说明当前前端结构 --- .../java/document_symbols_cache_v23-06-25.pkl | Bin 125967 -> 416535 bytes .serena/memories/blood_battle_scoring_v1.md | 23 +- .../game/service/GameSessionService.java | 3 +- .../ws/dto/PrivateTeachingMessage.java | 8 +- .../ws/service/GameMessagePublisher.java | 13 +- .../game/service/GameSessionServiceTest.java | 23 +- .../ws/service/GameMessagePublisherTest.java | 27 + docs/DEVELOPMENT_PLAN.md | 17 +- docs/H5_GAME_PAGE_ARCHITECTURE.md | 321 ++++++++++++ docs/SPRINT_01_ISSUES_BOARD.md | 76 ++- frontend/src/App.vue | 476 +++++------------- frontend/src/components/GameActionDock.vue | 85 ++++ frontend/src/components/GameMessageStack.vue | 70 +++ .../src/components/PublicEventTimeline.vue | 101 ++++ frontend/src/style.css | 152 +++++- frontend/src/types/game.ts | 143 ++++++ frontend/src/utils/gameUi.ts | 220 ++++++++ 17 files changed, 1325 insertions(+), 433 deletions(-) create mode 100644 docs/H5_GAME_PAGE_ARCHITECTURE.md create mode 100644 frontend/src/components/GameActionDock.vue create mode 100644 frontend/src/components/GameMessageStack.vue create mode 100644 frontend/src/components/PublicEventTimeline.vue create mode 100644 frontend/src/types/game.ts create mode 100644 frontend/src/utils/gameUi.ts diff --git a/.serena/cache/java/document_symbols_cache_v23-06-25.pkl b/.serena/cache/java/document_symbols_cache_v23-06-25.pkl index 6e3cf279738cd87d2cf32addc86e54675b7d2994..761610e822376a3bcf39164edfd77ccae2afe404 100644 GIT binary patch literal 416535 zcmeFa37A|*buTX4l8`J*@*>%?<)uBA?H+41t45pUWi&I=jHOwa8ChN=#GUTj)7|Rn zJM=P=983r}CJqh8Ff4IcY#bKb7zmG@B=7M;Nd9?XLqbAA2;1ik9^ns3fIx`z&Z*^g z-Ri2Y+oRERjXnD8n(lLN-CKQrRj1B5b?Ve>m%e?;k~5azzq@CmrQ61x*fBR7Z^`Fk zEt5_v+md#sotBt4*>a-j-j#H+lTN9L8Tz=_HY3Z?4Ha@d|X=z!`$xgU4SbbT(;N%MA(1<7Qd${HIyol> z4To>V(pm5e@rL9%`y;gHCi$}c%aSYb76$J5fPWd>ZuoXav6wtpxMLmAR{QYd zw+6UlU2=V(0oZflwdZ3OW#eGf%}u9bF1~X`;GGx3GnV>K#BV#KcOvcTz#ZG^JCj!? z8TFG2wfFVvVjW5@j#_WBkZWq4;?BaLszgA-x6VsXA0;bcu&>AUM;C3T}?8QS;_&7$|J%;5V=tCGduX(v;33SJK1wj%Jhi=Z;t zzwA-?4IC$;XrL!)85Pn(wZZd?fjqEud+zLkjc^w&j?GzQ#qC=rc z;C@*ld0_wY0KYE_@H?vDH{P+)=Qn;sXVQC1=Yy$xAeAo=8@G2FJXv7d*0--eRYwS} zn{u-gLH?IL4%G9|pDnl(ZjSW&APUz7P-s!0fOlN#qk!LtEVB`XZ`Tur32=qa7KOF| z3cD02;2mWY@SC+L{IHHFM3cqYVz4`aL9YS>yrYZ(ek1bV#*y%gI%3c;<>d2f6!dav zUx51u6x_!<%G}3qwvK@GS+GV}M*uD>X7k0VDKA%WwD_U`twL%7nV#EjDBN)(GMsXjdzq8jo+w> ziHt`7K0d$RjDC528GVJ5$zT`H(-kQ@mrMi@3VpJ z$@s9I+w&S-e2-{!AdV9Tj zeyqMce>r%CQ*j}LgDLX?g$BPc0na6IQ-s-B931{vF*Y6vfbV_UT=_2)EA^X<{G3XE^!oSeKdg9R~2aC9c8ren_X!AZhg_(%F%+!Bhiy{j`vCa zBE@i0;P6xchwmzIz&pw~;5TX{*fMI~T3;M?i8$=5@SG}8s3OC@A3)>R3N-MJG8*{p zN~>hp`|69v+Eji|#>pPj+h@NCu=oW9i}8-K3WeV`+NMH%q`qA4$WuQM56CN&5sl=@!1IPTUljOU4YU&(q5!U{i?{9^K}$!{f}Oa6QM3}AbD4Hg@*h+@%+#U3n% zusDoG3X5qhUXR6Fuy_v^k74l{EWU)r*Rl8x6eL{n&B@1UxZ=xa6>B!?777`65`s`h zX#is|SkY~bjkj-eoNe*8@m6PRyc2>K6WbF`tFxnH>vnhhJO?YLe*-^z|C027;U7)N zV*1x`5u(Nd_bw@>Ur>lnF1*GhK%GIh0Pzm@58 zp|R=nuvo@^?aSbAX?{CzdL=gCGAz!|awu#IDN_F)~CT zu)%KAFzU1jqO}A4fKOPE)~i8cWZ0CW28mslL1N@4D?#eA2&C08A%%tLLN!E;tQ|+} zx(pE`+c}N~EP|*hPqb~e;f?w3+3Hs+3yYDYpvGb&Qz(|J6^adN1h1uJ@TjQ=8LM`w zq);$2pR7t3pv~%A2H>zLW(qSlNypnQLbaKq+IwQk%e!%ZHDGqQHmY&mVZfDDlW}Ea zh;}41oc*{aEW$NPag~B)>Y$rw*gmHO~#dx z+ZPP3Z?Xtih(|~NB%~ROxlTqiyM_Ywx7DD&(*P=~CWFez3umxCXc4GK9T=r2M}7UW z9Q_E3UOpA$p__!*Xr>(tDsod~z?s_ZuQ{_1HySkOKTvmsPZ~M`t0vn%jJ$B>;}2QX z5w7%daX072d)!RH$wvvafx-agr`1q?!2l(z)*u-{jO>r;4PXQ*JD63L&?oDUr(1ug z^96O}0{Y$ui|LKngln_G57fi#Qgq!s0bpyb+6cVetVhJ^=-;=B57~mNNI|l5M_b4C?c1Ez`Ciof z8vJ@#)cZRA(Jksd2^VouPrMf#{(qw&2N(7D((~l$S;wP6%On@7xPUs7d?fu7lPyYi{Q!UPscCN~K-XueVHE@NxfgSWkuq+pN5hJ5R zQ`tUt{c?3RNMFL*mwYV!9c(B|Vlw|-@JIOedF+DeZ^H}yL?*P?oIL@-HNDvhOwUVu ziw2CXYA_fXf|t}_u(@xA zF!D}&P&xCg`VWzfIhN#9jA`9dgp9|K#JVav+tlbV@+~@a*!5-!9Y*fA2c0z*pmSjm z9as(VGSentwyS|*_g2qXlTJ%@=a8BX$D3pY*_hs)$^#Mud@ZML}tvu-I~g$##5P8AsjNh)yOdN9vw35`gIaAjND}pGPhcQ%-VwM zz_A}8=aL)Lh%mBttg!3ySYc#~#>%(_h^#~FaR9afIuq`2aXgdCCrx_rjcQmJdADw$ zu2dKude>yq=tf#hjmb}>zgGg7#Ty?ErH^$`a^LW?)9nk zl14|$4oSKJllsHNOtZc_rb)TOm3af?i`*C$7fzXK(G=N}@x1sR2N%#s16p}gM93vf zHX4dLNmFE4Jta0p#)1-=wqF(quxLk|!YkE2*DiH8ypxhKa#5+&uxhKMZpg@&+0za0 zt$#NxT})2y!H&pwHI{diQf!v1u{ol~CSkyaRg+z?j10SiiHInzg9TMGIK*>(U{hqB zo#bz|0I&@cZUN3$Qk?8l6|bZkuVV(hSTz|hMuyyIR^#lt<5w`?$EwNrF)~`}I3?RU-acXhe%DBOPC0R{^4q%)9#oz=$?R798N)m&H&j5)sTJ802!-xo;1oBnMYZPi8AXv!hUT5N*6oD zf`|T*JzgPjN`wKUFDNk@c-a{SkXSt#Bu3_L4~sy0+0y#Q(?nr|0UZ0@m5nnmwK$lV z0}jt!s*V{Xr&EJ=jsY~kqJ)}HhwP11916L(>O8jASpZveb=agcvkc&^(SUcp2|Qj= z29MJ%1n)`JaHq^c5$a>{v0E6^EVyvUM8BT1=4fiZW!J?i*(IjJIYB zAR9|Us>qBx?Bxp-w%$a-jRmK*;0`SG-qKr{f2&D8$<6?An+D>&CWv`O8DdVi5aR1C zfcSbkD!D!2r=kp~F7l!h&8ccYvQLBLttKRSMHxv>=Mh5|=`?*7AlXL;E6W{H9qU_EAnl~8Fmsgb0<@Bxg86}47_k%U9L81N<5ltfBPRH)=3^y9q;HQO1zdEyVC07GQWKah!sh)0I+NA%=PH zof^D8V8V-6l=0$p3x(z*7T|R=oKwe#RT(g#;N~}efEwBNJ}dL_LmD_AGr`F#t_v<( z3~)LR*)9bSzpJx*_2U*Gx;vJE1ce+^DkQt|!xg7p^E+w~rXSIu`6&~cyrMifIo)5N zx5wo8QwyNHzK}b~h9XC6j;6)WXpsD>2}xd2Mv~JlbnE=J1xPmLXfVt$tXIKS7d@JW z>G3HIZhvROjaR%{?FSlserdvoSCsML^h@|y!`aRbe12vD zKCLk?TS#S#Zdan<=GX{3lqw{>BE%9_Mmibb`;`X17fkT+iZXnhZlMS51q
  • Yi|8 z#R6)c#CG72QT;Nwei6JzbsE{eAkQhHe--vN#gYcJmz=2y;$ZY;Fy>Snjs|!|8EsC- z(^p7Ln?0)Os%7<$y%|>)S6yG8@=MJ9N8MHyaB#~s6#!Mni%c-z@v z27Ps+8jxkU4FF!O0kGKwAg?F`$my5b2jJBf0qCd4W>NThDQf_*RRiFd2|!*^29VP& z1hCx#06V!hv^VEXl3@n^I3_Qpcq6lE{GAVkK1sEP+ zQdB;^9pEH!`=aZOMQ{W!mBlNjyC&PM)tl4?j%uL&0~55oq6{sk2aJ_zFBPx9F?qlO zXs=<=O2>xyQZ~#cjDbJYfcAM4XuP5f8mBkdHy&@d0JJ6nw91LGmQA4pf1-iz+a~CE zMHxCykJtxY!UE_z>Eb>Pw`wsOHDUN24TirkVaO}W7;?IWPVl@17`E}@r#imHmz?Be zfKcM|KQ#DlV2P)-Hb1W@-eGp?35RT*A^Wn@pJU zin65Sbe?zJqA30kS%6_Ak;+2WJTlz+6N9cB5603M;Of%AHE4p1SCpe>INgWKs;HSy zT7cLla#&SQ&N<$jg^U%LObruG2Zo&iwP6ivcbHJ)73D$3=@zOZU$g+S2$|wWTql0A z4|cc>P2pNm_<8QI2Cjq&E?!Ye?>fNgJbim90`pwpWGShw7EE|gT7ctqWay1KCq~>v zF&hsIIS8e23;gUilMgJZLG(2yM0rJd0CKv8Hu*nU0Nh>zTzAsBCFOX*Ridihz9vv# zs{!>LCQx}r8B|WUFsZ`x7J%B2%AT((E`M-5)ez} zV@^)meFS680NTejp#2XMXuP5f8mHSimu<5EwB}TPR9ekpJ5+*!43ucV?^7E5K4Zd< zSCsMNbUe13gr`_F=(;VyZ)40WW=UG0X}7jzg3oEd`co5FyrMjzak{UM;Dna(vJ>@R z78tewsRnQ<$1!Upu5?kb%1VAy#4H{Erv|XUG6BXbULmc+ak{^*vmOAWUN|SSo16du z3r`j}xsF%>-&Mp}iJ8*qN@aPkEmKX52Eboy@cV`dKVDHfZE}Fq@fa&29k1}$<#MTM zIHW*osRc-;EkN=L9Edy)#fLF0ihr*`@!KX8c||EH`2eT;f$SHEqmaf?Sb*Gd3y{NA zxk}`?zu7ndzoUWfSrc@;;>D5x<#a!Kb}b)(*1&d;1+ZOM@CMvW{D3p=Ww)@#E_e?bTaA`6D`%QPXw6yf>YB}WPj*U(^F}G#XZ8>q* zq?3nzeJ$fo>=^8VZpr6jEt5_v+md#sotBt4*>a-j-UVTP?7j&|LfMk1=V!K1ZGz;X zVCP;nW3`#&BkAwe=e+gD28-$E;nnGX$6^WOtWKYU#Re=kVbO}kZY=t+7{lU~SY)s` zfyF&oyakK*VDT6hpTXiwSbQCe?_lwNvG@-xmcdTH^!Zp^fkhJ*JF(b{#V8i9#9|VQ zyRdiz7Bg5pjKxQ>_yQJxg~i`t@dGUWe=L53#Y%8#r!U0fDlA&DxB-h>usDQ89E(?B z@fs}Nh{e0G_y884z~X;m@f9ro5sPQB_%|$;KsQaV#^MqzuEk<27F}2jVsR%HNi2$3 zybg=|p>U7PMALtT4F3)k?!7bj&Xo4gszV7UqmiDpWv`RTyECPT8%rd{TjM)+#&))C z9dB*x*wNb2n&?dI7(=XQ~gOEamgJF|aDX=N-4>EvQCLf~7mbQTN|ydnJr{OtVz8~%at zjnbODn}O`Du+%-mpO=0dF3R^VDW*TBAmbyJ$K8UH%FHC!r#DSU=fSE2>_Z`Ocev*q7t={Am#bTO4t*U&Z3ep0Wt5DewaQkqY8Ob= z7@4;+khOQS+U|^#niTU(5YXUkM!?Ph z-4$x+t};N!s>#qXGKP*}2eS~mZ&(4{1pwV%I4cfy)qE8EF?Sv@;B>Vbr+x!YtQt5w z10$1>F;>M=oW5@bP8$-bT)r^qW$$ovUVk?3o(Kijel=Jl2C!JQb0x4C8GW4utmR<& zL6@PkO5J7QPpkmyBH}XZMhA61{Jb@$#_0|NPORGb5>AYau|fo=6%?mC+5BI3r2f+i zq*jB9>z)`&L_GR4EV5W@;GATi#R@ZjiVJWtD{cg)>E7M)4KFas*DP!(iGS4w!a_(X>0vOuNJA zlb|kg|4{FsDcLc;qb(Lo#Ma0yJ!2tz0U;5rJn1mHmX8I+E;W>i&gJ-rULW&n6bMisLOs;O*E z($H1OV(+w*DLMr&#~Qdo-N1|Ji@?eZyoix8EvT}6>^dAnsH%qBZ)y9eY1s~K4GP2h zbk7^H9cW^vTksQlCDN_%iQ`SoK*B(Be!3)EqJo0~3=0DLN;Ow6R&#YDC8O=GV%bfS|aN=a2n$-sktY+0@Rx|Pf>f85(X7y#oY#zcpyB*TC}; zHP7P)p0jE)&lwrD@CCy2cZcTrrnoA`ye4KR)Xcuiz-(4cW;P?^u`&yU*&hzgY<=RG zYKr!&)%<;(fxoO8WQ_^fhK!6!V+enxq%n3`ik}S4+N)?xn^V|x@*p^-`st<#vhzVqLM9^k*?S2D4C{LP(+TZZ(4+HZYh~gRLC_ z1~c*%oWcIN2X=Ay>CoJLIe0fyaR`wQ?s5v+fhhKvAm$R#cX)6Z_+Nuw_lO#fj~L*1 zdNpHg%Dy9SQ4YBI+e+1I`;9SJK|z)%jgg0M^E zIPQWDA)7+!Hs4qC{nrM*vuZNm85!eXY?eUlLi2rXD!(V=WRI0|V^-5+exqjQ3kGJg zYO)@~$QwB`?bTx26d9?K2eSdbqVrQe9F2`7vV2Eq_V45D_gPOl9uk0%Gt;cT0Uv%DgNWCtV;r~B&4N^l9#(Po)LVA#Z4DN3zAff-x1iY$vL+l@Eu!(s%B zS74FGVj7FrVew`x9>n5d;;o#U>v!`YFDcWiIp;dH`x$B9D} zTjzX-s-@xAL#Wy@{G$t1OTtAARTJ-pSelH2TrE^>o_8$L%}*21NE)f;&V8hs0%#Vf zc3>_$k19?r2EVIxK2-t4-I_CL7p_2p6?m___VqSU70Qy@x>@bByij8V!?;0{KLuZDeF)a+wq)DM*GW7i?$ zL4bXXjNTCo*>`_v_FaJNqxys&KwO=V&1ya}@-7`8+4XBAJ~Hx7JNfuvXg+StDUQ?H zgU27hUfQbtvPI2LM!rSIPjVq9>9x^hf8B+E^cD+IBgN%$h z!YmxgUk}a0i>5NpNjGaFd_Q&T!^Kcz1j|1QvcP3nNj8Z;nnxeO< z8Og}Ib)Au2zgFsujC{SFo$+5oGjau6V8~Zj6}PLI$H>Dv=CSLWCFU`5hn>v(uh7hU zw$jp%j+BjRdN47y5b0;h+>uYCrfUdHcb6}6MHKwmvR2g;5jIB=f44Z_HARNi!W*(_ z1SJt|q&%!(C7LK5E7i_{o$9Xfa!SVKB&CpN)nK<(K*%#P?l`w_!d$U3taIRElH&;b z2Xm4O%MgT{%hlW*Rde&Oft#$F>?>eoTnj>Ome%ob#DWmK!+hqJ_+Ksv&EgFcZebLE zNNpT`g_^^Tfy1nt%wa~xfX{`(;VVLOI6CF#5|Gn{t+&x77EzLww+vS6(=j#2(*};S zYBI+e8LgRx!tu?aIev|_7lN}q_?Js$jSR`C`JFZJn^lwf&B&V<5WlyF=J!=>9|VSq zN@66Wz@AbwJ7-`vt0ps>kF&NnwAZz%fNSkEWbT|~6yWzG%RAck2)olJ91Djd3^%9#I*`FB|TOlk_IqnS2 z<4pyRXdIx6Pr+e}ZZN8l3TcCIe!ZH>4;q-vs>wb&M#g=C3q@W`gy!~@lisvD=3&-< z+$%wtEpP3q)}P<4X7$GntY+09o+n_LFfw0OEg$)7)$(ONGCl_dB>T5=ya zi_vvvwO>)1#|(<`rcobGolB+u5fd z3C-j&NdF$sxOnCsWpr;M0lNdGj6L)crXWz5;!Tm-dVW*`$_*w^ctsf$PPZJCkA(*1 zdfM~3%I60mOQ`Vc6dnJ-uWC}~CJpZgOuXk6W!`f-U$V7yHvK|q-uKY~(L<9Jl*ffA zb>?6Y3~7KEHvz#b%0O_s<%8faLj$puNhU}=6B-4>bP(4tJ!N7#uP8H}(|wE6>N9 zLo6~u)Ao#zbnchwZL0@sMSbmp=<*zldoL7`t&gr-v#bVRy zm!VnS=|Sc~E_GBp4*9hjbe-2}xLz`GomZ5(&gqtO{j9UYt{7iQ{4p@WcBRxhra^wc zS;OIXn>frX${gl&%SFlB&>X%QlCJQsTV^p(Fu<4R2qDW7a_mJYb^g5?03J30z$?Or zX3^s~!09|%OL6vyC2pIIp)uGU%XoP|ZwTdUSAMu6r6I?u1`+d!hW{Ti@t;?e2LPx0 zD@ArHl-Gu4{`H02N!Hytf9FWNe^kT!FPV7HE6Tj*bjwZIj?lbs%+Zz4VK~_gVn6l# zH;DN!YdHNC6Q_Aa*uNi;GMs(`S6>YK_bm}QH-+Z>mK>Gv%!?o%Dpht6hg^E<_s`{!XnvuA=FBq<%TTdFYoi#>>mFt4)CLiZURaj{7z&_7(hAXh7Q8SOK+0 z%CQw38D)SBV_>a@KDR^(rnZfISS*I3>|&HhU+6HuJejA*Ezk#?vDPx&|KfkWQ&|F zG1$#dan2pnz;KTV23}Eyfzt!}r+stoHbDUUoV&X8bD0^`^-Crw0FD?lMEI04glzW^Xr)LST|1C7HJ88Uc zdG9j`qn3j3%2z>0EM`n9_@x>Yt~H^+E6VbN(|K|(O9Sx!J2clX&4ZUQ6W^Q4rt(SB zu|s$9Mh$Z}o0!Wh%FCgg?z_q>6Fghet5>ZGyXb#W%*j^h)kf2Ci-xr=Cf4$bGHW^A z4_CLAwHJkEZF4F=D(M+)u~TI~8jSTe4aYl79Oo5fj&phw*Ql-K_*J1fzA@$%vm|nT znjCmUg>#tcuv5d{ohJ73in1W)biYI6B2dQ5PT1x6ZVAoX20Z8rcfz69iL$pWiJKyz zGaGvF^%@rUm{`m!ULompobGEJ>w(2XQ@G6GWOn1qDyl@5=^NLFX82V^x5F(i)X!HA z7-B07Mk&2l!|`Dg$9Y95E#LsBW8y4gg(Cm$C*gI)rFRv)Gm(zVE+HUq9GqG1n zpkJ4i(RXPRhOzwlufrSOgvGnCcm#`&V(~dFzJ$e7SbPhMXR!Dw7XOLGatzx(4~xsN zXvCrui!Lk%u(%xy7mHV6aW@wCVexh>9>(GcEIx|N3r-67GK2Tt62Oa7SCYuGc5iKinv?e+eJI38@<1x2=z7GKSUHI8>0KmQYM|S|g>)|3E03hBAY0BTA zAXhs8Ad!G*vt2DMEj_zh4tcp_qf<`IZJBgiPTV!=GGwVs114SH2HZx0ib7q@T?2?C&Jk z!(b8F{u=ltrB%XfXVR|)5+wQm9fm*vpp_I%8K5{Np8+b}eX43_)9aAqKY%MEZ-8oV zf@-Q9|D=8Afqwy(Pl+QCW8iQjJZa@}bt})IuLH+#;B}W#@-kg3Sv5GQB~Xo#c`Ft1 z>u-a`2b2EKZ+CnjzMo|Ke+zyh+5R7ZPaN+u($^49Vysbj#z{?zQKW>ikW3NyYhdpc zYW7}bU@xmCvzL+4*@&IMLiRo$n!OhQd-p=BIaHDIQ7~ukJYwMG)oNb$8+gg8fj=hD zRT&uC@R>5pu-X_uT*n+ zy_(AxQ8JoaN-nc%kUKrVWk$vz8=JX&NoX!#OLQ+V3)rS7wgSMmXp=ZGrbiGaFIO}9 zVl|Vu8<@sYuUH1Dq>F%F~P=|suXpY4N;eg48^DE1fB?0>a^{j3_8H39ZBGG;*`-9Kp7 z*d}xOO?;NjocgK*%e1X!X3uK8VFMOdVbOxc^;qnKf|x!30#ig9QJ`kekf@#(+4%vs{ji`U*g3{gN!{d3l5RA$U!_(i3)QV*L+zXHf$x*TPQ1OPjmf-3`d10|#BGEt@4atUYl zwDnN!7*tc4E~Je?(}lEeg}Qy`(1w8N66jEje50;?>^hh(s%mJu(DqT&!_=%V+9()RzD};F+G>mt|jd>H<)LkwlvCqbxTpR)-vyhAgCoy?t11uN9 zGhQolNlM0@I@xeVbeA+`}aaKdoyQt8glo77k>6kzDCXD z-3BIKt7bAIV-%CEOr9|@S<(;Yf6V2zEo3^uZ{ZF1V)0feh!Suoe#1UYm4JOrpod#e zcc)>SREwXeCOG-;Xmi`QCC1}!YkWMmHQw0 z3fu9Trha44f{Mv(%6}YMMyLEcOjG{s0cBHt*D3c5)fE3B_ywhveh->SKMDkx+#h2+ z4?wbt!Y4!W043w({)sBp)<{UQr{SW@r=gl^awn}@j@_^yvXYRdtxz}Z9NLBz(mNO# z6$oY1*!8vgYP4x|UjGwlX1%)FU*WrPJ};g2u9p5hyept5&|X3Y0wzYO4#jFU6pV~V zq$r_a*DsWy5USY%#W(5>#qvD(cLu%cIIL0Qz{nTsaA4QLmk^M zj|rigEl~VN-J#g-CH%t@eZ7JnNYbAzxD#%!DKg+p?dItdhQ`qp!q`Is+;a8&em(^b z?Dl|ET1UxfQYyy~t0s>jM!p=XA#kO0z%T8$mBr1}bFyE$y55uhMpU}M6Oe;J<-@%Qj83qK0A>*ukJ`)nQ>yrV2=(AA^|G0{uv6dOVxNq40y3>GG2^~ z!3I{2tjp_;*QHZQC+~JcWc-AHS170&)KFbxfQnU@%Qj82vDm#(t ztvgZ|7hv=B=l=xk0@NPDM$EwNTF>KT%4;m0<)nr5&8CMal)KE^=9nsBs zw@{$z4Z9%EVM+i~k)N!ZFg>J3?v)1QSTz|rM#i(^twio#3y_O>xeB{YkezHM1d2h| z99JWkH6X{T$;dIXwK~k(>W>@=iKQZRm^$oz^Po0qIi*JKE(3C`8brqioR^G@(c)yx zT?k%Cg=f?Pf8;}T$8cl8qX6PeM`CJwG4=t!8AjWy)tKF5z>HP9M8b@baT^7}Y$aeu zCt3?&dZO+yU0d)*%VLrbHj-gLpqrfi_o^X#ivcoLZHojMBX@DgF6BY!im}FH(Cg{^ z|9rhrl;Wr6FO25T=6%tM#bbRLU;H;%dVsR-JjaY2Mq8p20ERJB2 z#^N{@_h9h=7VpL4aV$Oy1qt%{AU+fO6biP}d)I$@ZpToR`}g+aBK* zPi&8mw{6?jzGFx0jp z__3#9ewXh+HC1>QX`dh7MVhuk-L%!T4Y2$hcn2e6Vh&}~*mcNrrmBXaNVI9m$I?$@ z8|S@u{S@Ci54|h>19%bX8zgBBWqi)_CNZwA8spcf8PCXC#vb5g>yVW=(D@k|_wLjggQhxSun|_x#B-ggCxfo2RYzl;8VyFq zQ<;?`f?bDmKmuqm^6vVhv7?S?tex`m1w2`(8;;I}r0LZWSg%IlLQ2LA{YnJbb$K#i zc6m$UQnJ*!9iQU|{6#`a>~XM<^~AtV7=^jx>HU z15uP#s;#n%)qq?{$rzNY92=|}WT**9Cq~AN33Z+;4%ZQoRTC}n4s9KhGL5W3RVqL=mv%aBXe&BNuk@Hg*~s3skClm;&pYz;%vZzp7Ui8 zO)Y91+6*|bYU?E&7#TC1;>6(Z)*wx(9}@T15eb|n$O35IG$cCIP;?rgVAVECP%tw3 zo(L4@^R+abvPgq&tREont|K590wCo89%gBqu-UH0W~TugR!#PIF!DBz&BYv>a=S*z z!H0E(=+Qbt6ak2a^@g|!tn1Za-CzKVRf|XiiIFkk3>ipxA}63T1x64%sfoD<>Ne4Q zx{gq71*cUts8pOXOi4GX;p;NM$Ev~3&wy&c$jux+JVA~e<6bwo{=ANGt;3NObgGy} zQ@0w9UIQGgnmjErGDa`@(~{Jn`b|q;t0OX3;Xsm*k-i(o%|10y{RW^|HF?}HGOnQz zpz1$v{<)6mTn6JND9XyNFw+3quf}D-fD5a(Ns?!bd=tlIBgiuhkMhGK%fWYb)E<6R zN35=hWn3pWTeODMXuaHk7ON(YB1ZPbUHwPVFYAcRg#;N%s+oq*s2Y%i20&Of84yPH z&7%4PvUKgdud%!HA3pQ!{6=v=SXKHb@XGYBu~-g$K7BqGmt%1q7Td7cgT){gcVLmg zB9Fyuv3L^}4`T5lEIx_F7qNH}i+{r6c`Sa8#hCyji6DFu--t0vG=h*Hkr6Jd7(aF` zD7~nGv8R~I#NC_}QfS!`84+Lu#1vkMe{^|cFNcd5Qz+gGp==EbaxiZ!@3!^ICk#P` zmq8;T49s{e#@xa^ll)+bpNTQYPY#43UJSp^a2m!&xCO!zFJ)i_usoOIE@OE&C1Y6P zCRMfb=yeE740HiT#;A}eR8xf|lJ*8u%98f2P`B>_`XY!;47`Yu@iYu&``C3zDXXf6 z!7j9YU{N+>Ls@KM9ee36Y(_nLX}SsCT#jBO_|$O71_5uS8oc#t@GhccT*6d>$Ev{+ zW&k`!#@!*dfw$Kt@WP+nL9kn{#_nP@cH0fuv1)KuL;yQR#@$$mT`9!b9rg^(5u4DR z#gQP@VBV>Q`E~=$teOlnBjb@E3lHX_Ho+X?q>*Z{-l>K)XMmMegT0i2(aXqq(g=ao zaMFl9BR69cq~Q)QsfKDnjq0lnsIqEcR|imKWQSiG10&WGS{J^FI` z{n(06WAO+!VII40`UCjZ4?opv%s2R4#ahia1=Dn`~q#jeXx zF|x%_{cmKNO;BAtp7Ff+9tYD6VeSPu$|uwqyIKt#Bjkk&?{E%hl6eryAIOl#I?v<#e}=UWaqE0@EEM-^_p& z&e5`M+I!YEfZHj+EEO=od%*Li_5g}w9>J9$yvHl#yNU%5Q8yvmuSWI`1G2ZMk!56b zUE+|n7}iJT?yug+cdtIm{(Lmdts@iR8g-Dgjyg=mi+nIU}}*)z}_0V0%Q3 zEhFQ`Tg$Osa;{|>P>Xohp1k&5NCM=eUNIMgtU7p@j%mnFs$qS#0oJS2) zJl|6t&$sVba>f$;xBq@Mu#DVd|9HLDHefqNU=L=GWxeCs@;r;!);`C+PmS^u)luI6 zkQ!V@#=HoY3*)V}fxC@@JM0wm{G^P%8SnVPES$tZRO4YMpLg?o_N6grrZN06HM(Ch zp!;z(x{Qp;GAu{8%Qkec<3krb>EO+F!IsU&$KU|ie8SC*!3hpy9w3@^b0$QeR3rK$ z1ENo<5oP2y`w<lP*wn#;+tnKNE6OmH8>BOaPHLL%;}hA z$MVUyXdBLK@*O>vnmXix>)0va819NsJJ}d`mB+k6NM41LunE->4XVdXsE%q-<@5z0 ztzTyws@&n|5|s+A+8#KsEFiThvS=bvA#K8UQiJcCO!#Is_;UIJa5~;^8@@LMW@E$` zCgYgvWZXE@w)VU36b@S=lSkYsIKR`KgzydQ2PUZR*FgOt6Vz|fK+WlU?HADRwhe0L zUnc4@VmpdwdiE6^6|j9p6SR+Np#6#o+Q&4|a{2;rMm}mAwA=Y8#B9D4>Ztn`T`iee{LJJ*YnX!pdInX$6vt)=@&FGUwdYCx5vPLYhdPd+>c`U2>zOFFf-qCaQ+>3yrC=))WE=- z8a1p&hNSH3=r|_db5cF21CDol(H+VjaPkG5a=HCv z8n^p3sHRM)9?+o5=?lOF{*i5{vXx#kZV9Sn%yLNC*flH6$26GUYr-_6!IaY%z#{K2 zZNqd2pK=q$EHQKmy7|3cZqS1qM@}5p7pMd4kdw*uo=C!iVF3<5tsVh6@Ov7JA2VTm zp9W)2$4p0-&$mm@vn-zKW{NRcE$U7>x1<~oSMNoD$Gq8N{Sgh;Uo~O~>_BSSAzor3})3@3W?B%ur%T^0v z-6(d9M`ieC5r}B?Lgu?BU{R2`u`qlCutwnNpSWjqQof+Ud-GW)BT0kze>3_5SS{Ra z8{QpsAa`T-IueT$?<)fCg$C<}PVcZE*IwIjJ)h(gC#Su# zV_6KE^1T}L-eN-U4eW*u!R;Pg329zYMw-*x?MFIe8`2x;9E@;peCk$2H?9Bxv5T!Y`IO!)DN z@-XD|OT=M_?XeHMdu; zma9V#*@ojLh9h5*n+2ZV)ZqD?2~S>8#*@<*fO`9cZFpYC@C-yR21X}WX$|uBhZ~8&LmK1L~@!1{W`{D1*xBn3vmf(fgOyg9_{E zvjKHA15bABUSqwuMB1hS@@5l|yrK*wr{l3Tmdo6q*aqYluI@^aqya!fYDjxDz>b)J zudH*K7W)Q@;Y4AN_!k1UvEP0SP-46oC(_jyz@`c^y zP5Ica8}0KFuD1>Et0D6;gg2BKx7W#bdvV`CNS0I$03X!=oHhacN_N9l62P49Z>U`B z0}O!}*$E4QZLkDb*OFAsAE|fdLQK*LN$x+1?f734x!WAQ62mSgX`0E-9|BzOOt z@TXyJZkoG4aB$gNq;20amMgk5rHE~*`)>pWK?o`n9A6OV zD2$B97g*X+e#eH6a$D-mB};;RMR0WSPBKw(5(N_)mG?%^jtKvRIi)FZ0jC$N+?$<9 zW!)%#?Qvtr_Bt^*Q3g`0H>z)^`C^B2UJT-Xyj-9UR@UJAT4*hZM1V~z?40YGYQl3c z?L09d@P!gb5?#kr^mVwPeZ7W8i)xxKDejc!b8z&f;j4$Zz?AgJ`S8$w?=7k`Ha7wSq7b1Wj`+2hq7M z-c005xBqp-eN_<}6>Ug*`NEdAhNegXZ2iW@>-{%~k12Q)u(y%yV~rxc8H;|{s@z1B z$fS=pfm zjn0G|D&^{JSicH09L&&x$Khz$;btKJl7C!|{~$io@ZXJ`BnVa9!TA=Hk60fUckmFN z=0jbB%~@nQl~#C7Y_z~;zqLw64YBGKu_rc(ZET9*`+d<`$vh(4SO-;eQ251B zxqVFn`-J<%SIRP&y=%5&m{*S#b9inlZtB7Fe?%E9a$NUzHDJ=@htmoE(>(+rfxY(7 zD=^@o+bd?_gcGqLoox(1h3x0KWNSRUs+#matd{=A4ATFITKY3GE_+%o{U5VJ`tQ=| z&m%QKyeR(-@*hPb6PL{9Md>T5nzhS#Up|&cIvyc+JiF&)G};*1a-&M$pK^1Mk8qMY zDuON)6dDz+@Lz&g@gMi6&?i(HBn`v8ppt@!efqQaJ^k4?z^67r%`JbVPm4sra_G;( zYawOvP$Igg2+2QSTIoFzbEhy}1heZan$ZjqQ#l7RN@ORZ1^m^{c*e!w9IbptW5mCs z5ob4y&H$NL`Aorx+1EADd$fyKFh>vd5B3ZlIyyKsc69I1!NDG`a4Q6)pW_p=BXA3h zdxexIz*UotlWN)cYJ+Uds%0Z1V}4J|W#eb9kd2*M+31EucTp@ZibdxCsT7OZVkT3T zL27YiyJQ^h8tEE1+CO+}*Ma_7ibc%{iq*v8Yt&+~WDtwLtrm-nya2@Fm#q+s{|DA_ zlrGm1g;-RgyC@blV$s(~EYs+^Mn^%9LxHuBwYlNGuF>8Q!m5UIL90orH>;)8dkj+Q zezlZhWXu$6x$^c6E2Px5wJ2}F<6alVlSVvolkKHs%!;C~^`)Y&&GrD@YU1#HYH|34 zK^#7$7KeUIQ(vPr7iR!$&0d4BO5JM-3|`!9~?R~$UIrT68B=TJm4CbC1WiM4ZtOvFlGPiU>6Cnown2!Tu!7$dX%~LktYZ(* z$ze_Sfxv`eHyelbE_qoAgN^ADJ|y`V1Sbvbs_;it*^?9%gC?2{kqCbM%DzV)gNfbM zz0a_~$a-$MPx^1-gGsZ%C=8c|VVzYbpeFqAnpkMyAO7%a%;Uim_-lkF_)H5EL84e! zWam-=^cWuw9bp0vT_4Li*?hvy;n}WZ9_GKp$nlwUHTC65wfgcSgZlE6T76+;Ohjq9 z`tpJm>dQ}b>Pr}47gd&;qoTP?SyWj_xUn!VoFy^0ry{DP(U$+;j0%6-bO)fXL0La9QY(!f*3Y z(ToHkV6>x#CrrZ8#H2kj+7JlRf&i?{$!NoK@BjF&hR9}76=%;isxs{8V{*isu)__{ z-!!Q*KUS(T181LM&}IHbt;;a-0?=j7xxj)A{2pr(bbbi0D|8RSi}h4r=X z`<{n1`UZ*wp?QLq*Elhcy=;Vko(bcVQSg`Or*ClDtt>kLy7svm<#v-r9^H z9_{JvKF~GN+tWmo58z|T#nIleu>-vWy@O*%yM~7k^egV@9vbOA+T90F>Fv?0QS4Yv zHre(S!5@SQVk%p7g-jZFEFV8j6~oY{$=P7}5wZu!pkNK0!!#)=bJ9S&LBkqY&2Er( zxBGNFR?~8S(Ir;sSpT}Rx{ftV*k5AW*BmdobqGstpS@4Jf*?tW@)g_xtE`aTLPK*mn>*Q;mAL&>PH4w}y4czG9e_ zL;OL4`b!K0s99yPx0+54H3g&KxR<4?%wv$srrHv7u$saU5c{B_wURef@qjIwCkPBD z>iE2<`+Wybzp}rZCN+}o}2`cuuK@+d~O1=&(J{Y#iHH0oM=V{_bv)Pgw| zC23hHGuQ0U)Lhj01T9M7XypK5+RLKON7XJrzuw;d!T!;{UVp!}M(H!DZu+I!(POEp zLtYjGEzy726`zLe91wUo=HWR-7;R=!Dn>L)#W9moF{)82IDG-E&Tg|psrZ>rsR#q^ zOH75BV^7_pe=uAQ(?$Pau7I=Ni+l6JsW|W!wtvv9BevC|BXT#EFt1n3+Bj?H9_VC2 z^*oSFm?&jEG@YbcGZWN1KTR-pg5VC6fHO^erR)%5?+W9?QfM?hVIhs1AB9A?Jvrxi zVA+gnHD&jSNw=BQ=r(UM={8x7Zo}ydAZcNb6}rvCE2^)GhJ<-h%kfi;&tl`@qL#C$ zno?UP$%eibGO_k24pH)TV-)jv&_bq71=^vqZmMkG>o6azvUo6 zKT@uvDYB)F|1Ws|gmwvP^NVW{? zK|aLDInt=Jn$*(!HEQXHOls*{G-@fQ@3ns&Z^R0<^w2W1T3Q?8b<EWKjUA;V{I#bFYN2*7)o*1lN?OSr+t8e=|)pSq{<`+b&EJWC|j2=c*Hk zLL7`k>QVkiz>M;s3T3bj`3C$0$5uqOa(vrcQGZ%%hsg z^DCyw^D)ij$>|FqtMDtWm^`1<`E)`fT(`+`j+c)@W^|h4w=TMK7Tr0E?i@Znqc$^2 zRX?8T3?^s8aV;+K`iDs zm7R7nDdOzKcR_;_a8f*e#Ib(>9u8+KXHfujdsC^hw5gH+lPsY4E|;UnA{*W0Lki&`5huUjUwpS6dJv0DD&tsD59pzL$Z`I{7uNCN*m`*z8OAm`NTUA^@>5)`b^>-euE?(52?>lCT5JGuQ>9{wJ(c&K~o z=~8or&R?r~M%s(r*`$*CKQNzna|KwfEl`j2=?Z_T7KL7^-6-^^J}1?aI^cM>7u})k0ViJ|0*P%BG$}>i;>uik=T5uJrzB8abrE@qTqcJ_8jtZThWn2f2jx}q!^R3kR zD$1{DJX&01W1cPZ4!UC+mF-@W%9hcnY@FU={{r!Gr}J$3gPOYmR%18bqU z1sS z`B)C_iQ=1_k!^F~$WkqPR-fw?onY3kpBi0EwmO4OmqX{YEqKjFIjHW<*};R^62&ap zvp}Yg{9Z2yXAzIMPW&XQRhS?edspv?q*KfnTu3QbeY?`{X%vOWOp3yN8byKAZ?a!e z_}}$V6e{$CZ|Sz$kGQo${Swm`7Ck+F#>g>mVj|-bF)-r3stCI!qYb`Zuo%YHz*Zv` z50c9`C*9nZD*D`9sEaQOPtTZhqAY<=WkFTHn7Nasm3lEOX4c@<;$5sJ^%_})v0Y}T z7;}=SSAWB6(lR#6Z7+g5D_?m^b2YydYo+oSIm3}6(4p8yc-Z4s%oOrN334bhgP{n>NW`#oAsN0$`d&n0RTVK;# z%niP%*v3!#O8%l^OGXA)@D~+ZIRmH=al|6~_)HsGr>%U%DL87?dLsI+_#W-KKjiP! zox^QZ{hY0Hm{k|!$JBhN?itp2bLU3kC2bVdYgystdZB8ow?Shi5q-Qq(<`fOBM|wl zYliq3IJrlmerujmtNPsC*LCz3I1OD;pEXYo;q^`Bdncy~C;8!(3jKHs(~mP5@Av>{ z_bJ$t+*iaA=jQT5*-_ZToN*a1P2Sz7G|KRAOv>=rG|DiiZ?#_;{*D#OaB`I<3wc*K zd>1v}Ma{RY?Jc@=%e+|Bd>1v}8zQiuc$Ds&sW{zkPSw^~?TTr)HA0~&f-B#?+xev% zall>C_1q#uBFxF*Mmz?k)tGomupZ3Y!0WVwxtfo;9FZor?oXFTZX1&xWd`7Bc^D2<8r-;BQB zeiQ4*R+w17f40WNIyKlAEw4q(t88s7T3%&dELvWRmRDHIi@2;|ri~mY4ovj(LtuuW z%?Mkd#62PIJIFF>QMKN4vX5dZ105Ulqr3G`Am@6B~xRMn~ZXquI~CZtWrmb-CKIZ*?}XzWHHj^&Ev6hQi$0~#^0 z5wKFV)CU1jH7px-;Y5sh)s>;s%sY&=YzTq9-#w94EQ^CMl26&Z4RE3wTgBBmp)IxH zsW3u1s1eeQn>u-a0Ar+(OT`M15|3COD4oj2h-q183$rccELC}v&C$$mkh4^A`Y!uz zj$d10b9{S^#^$IANG2? zr&rnc)m(P^R30=wI2OuvN1X%@=LuyQcQoRX2?p=sj%*$@*Gj0M@1x`#g!3|agb^AU zHFOwNv#1%#t(Ex)MzSQ-O{}mfRSdtw&3U!*n{xJa@>^;Z-Q~jB4IT{5k)1o(4i$c< z))I6I_=dXJg1$~8Sb~UO#ZT75CzxWyIv!=!+3%@l(TRQ;;W1{5Zm*Nc&vpbg5L{?J zOTQ1!|DYk#OfbJ$t19?x=sokDVTdVywK|SS&!|&d38w?=_hu&%Yp=K#T-zG7pG7Hr z+$(=)EZ`6&9;x0}6`jnO=Wh#Bcrlu&7o&WJCb}yiE)^0Epls~VLssmXm& z)VMG1F}W|MHSP;e@37x}v0@!$2YLij1eH=fyAB`na>queoS1uf(mj0Qu1QGD>E;fP z!#R&`Hr|rY#ai&-+m^I5?Xo!Awh-x0Bca$C*<(<( z8LQ4z?!9Ddoi6OdcF{ONL6}6rj>g&QtvG$srLqU!-it|W=JS@ z_e?Z>Bm5oU^ad{V)czxzGyXcfpw;{P5_v9PsIolfx-1A8jaRV@VemX&Pw9oyw)A;oNbN zqhhovGviPO_s1IriCD;;6ux2C-8>#{@OxdxSBKRvkN#CQfQZ8`v^P zp63hXV&kuSgEnp#wMQzLCK3D3hH zIU1N-gjH93Q_d|Eb6NknekI^cxk-0NVt9~xBnoL2GH~R2YT6wFlitBaklqt9cM3m5 zT>eSqDZZnS%z4KnD3AIx8F#|TjAH6^zhboE*>^tl?EiTC^B?=dGxxva*-yRk`H#K* zxtR|?^Rdr8^T)4!`cEEw{$uZf1*!7Wg?164r%-v?s;uSG=@t(OS(alQDS+G*Tr7B$ zb|Dh;ih#T@c}%5~5OFVO%7*3FC`cU||d9(Or90q7T zH|N}#3&Y2Mu<*3Ib-npUvloc|YLc;z|7P+Vo12>rUHcC-UHc(Z*X9-FuFdJ~_IK@e z8@l#w_d)v6K<};zZZ5hx?8>eV-5_$zb*In+Gw9{To=N&}V9bVx;jy_NazlD!^OAD55R{&%tIlSExKjlj#N4%@Du*yPZd3Slvn);GDcXXD;XGM#6@nn zLAX)X|Ct_!x}a_dlm`R5If1@TdbdIcIeeQ`1$)jA=;C7YlnU` z0UKcOhaXB6k_W*2g9Dbbd%a?=+nY+k!XuyV4XWX1G!o%UCW*i+ULeh1obI0m2|bK-xp2=V&j!7b*Af-9M!6AqGyW?#Wb@|WB#Bqc(_`f~oQcb|!AjezdIz6_ zfkhP>Wbl3Eca=Yxy&S}|@$7wXeD1!|v-jNp%pbkuncw~J4`2V`r$77oXa4XHpZSxA zo_R|NF7Y9*n9?s*#M6fy%U{-v<-aqHWnNL9-8ua#adx-UgORpkBwqk4H+*&{PK+vG zR*YSh>EoX@gZ&-&Whw}}mmQFh27Tbo9!Kdk2RO^vjB&Lr)4MOoT$ z`U3FRp0GmNy<8*hs)4#F^{5;VFUe~aD#Qk=S*DN?yD0Uj897E|WzABL)bgcX$l02{ z%de%7mDe#@+3jUxu9I=e`62;H$^F6xTknS&iTNv&#N-uaiOK1W_UniDS|Ks7)<{e} zv_xKVd?OQZ>@r$+8vhW{iGs#n`LseVcZhY!9|YE3*@ei7;?v7Ek?$H67Q(iMTLj|| z7Q8ZLn<}g#H!F>{R*6Q0XRQ)igz?p+U?Y=) z`(Ox+f=gkdujtH12>z!=2(DUc$OywL%0iISqxK8I87qWfgGLBykzJI5+_gO`890BH z=0)1RSoK?@&0HY?;qcknNWj%h(#iYNFEdF%UQw2SoW1}g;QJSb1eB3olz@v8P+H}D z5nA|03CJzs%EQ0-3JJHA43_W}8WGrH5`ny;ECM-w0a(Hxw?YK!EMYygi!$((WMHlG zf~Z+FyTHg0u2l`B_Ves4;n`WIwW`F_NmnZiXXi#FjQ4+*!XJ1UvxEaD#c%;=5PWSK z!FRJs@bQYW;N$d7_FKPyW`*Eeud#lWXcCzxhB1Wl@P+lL)jCc|&#ZNr2D@`eZxEFT zHIGsveQF-1q*l$6j`+rEO9umg;9<`8_DC29cHLH~m(m>zWst|Nr z_#!dv2Fth@G3R8^Ni-XycaKK!jhF-yU6I_1VXndkFYydRcoFUsD6aq)q_t%xjuhQ)E7VmU za~sI79fZE?-;CWts7rc@upNu<3!$AJdvY1MJ^<3o!C8&Xu;ayb zCZmds7L(2t>{AT347X&+G~BJvdNVcw5 zAuGj=jjVN3&dH4DKpIvQ3Yd$7kKZ1*;G{AQsu7G2kCV&ThN#4PG1Ufp8N7|&PhXO7 zvXJaU(`vkkkHx=wUZW(zf6dsIRKcCZDs<0J^R$yGxcnlXV zmA5o|xn9^hx(~P${Gq#cd5Ma7-L)hK%`m3Jse1yh8-KWD3Ca2*CT0=xcEJoq<_E5| z!EP-l6B+TmNp@2tCw#tvY*ZAk^0^>by|37dJ-~rUEs_*I)9S)ib#*wyz>Dv3a2Sln zJlq1x+cK`bLLaTI85NqrdQ7m%!u-TvHc^p`20{$cG_1Tg8kl1Rjs^2v@yfl~iB#5& z;#Ww-b8N2@qtkz*`gWQUYdGg&4jwNT_$`$+`0-=37VJiVv&HrlQ<*r-_#zifHQ_m4 zW*RCfS~cp+D8{U)78$9w_P$_qgoXw$AkRR^Kti60ll%@}fKBoG)2>1Z--kETRi{qJ8J_&4@go8Gsqp8U$dIg>^=1xLZ$%0GMEHPOJ zy^VB^n=wls`9yY8(QW;ZStwV)r*~!Jy|B4BhwX+Zb>Sg#eZ@&93mXt%uE-yNOnta5 z7E4tUjV-wjFf1F72y?`2!b;}lEm2=GGoYB9B~FxZINDW0@L+D@%+ zZf;lW9`R8e5$=O# z`>&J-DSOv!$0@H~C9$u_aoyX6kuCrxNk=Nl0@7Gy7i1=zgws@^>!BBrZ1<;UUU6yB z$@79#6AmL@vEUB6$EmJ_<{;#rfR&Vqi z1StBl4cTaidtpk05%~0H@53A}xF+VeRAJ>VoP@N4Lt{tx4jmlq;S+Y%@#CT=gR3pv z;wU6xSdBvdmKgK~no(_W=qwJMd|^u)4w;If^I}NnJlr+XHE^_l@Yb#a{j(lAL&ZWq z6?ccJPBje1258@lLq{`oe0f{eGpv{y#8W&M?yNA}*EQOEsvUZ74*iJSu3PNMnx4#c z-ItR7B#x5Sg|0izQu-gv9?N>ivt{c8$IGJSK@S>%l_%yLjiOIN?U=B14mdcte{kr~ z;L$GXT;yjuzZevq8dRKNr3#}7izk<*Fu+-{(26m`^e(q1Y{{Nqwb z!<7U$4W~J98V>1*&GCZQd<(1Q`_a)uZFFCHsj_Fv%pd*;|tFEpG#*&kVzZYyTQt36g>M(ydG?@sC20v~*D zGlqULN4NlFC0DSn))R_uM)dIeOAeevgY@ZwJF+P<%M(Qd z&w$WA^ygLSl%jJ`-7|#;o}w>RV1oJ7c&{*6%w+gVH+Ijmy%H5$UG0EDbgP zjXgKSoQHcDVb1G%}>5an6G{zDR?hpf1jN`C^W&$M!k-0X!oC zj8$Yx9=4UJc30ujEp8}<$vs+O(}|*I*1<87O*e0I9ty2G&UyQ~Na%nt6NH)b$h?3p zpY3Et>I$JZiu1fhOI@ClSoLzBXocNMRL+Y*dRsy~gRciQ>nEjW-}UBa-}8`WUXtuM zq5G0u9XQST8U)uxn%xr+H4JBg!g)get_<=)+#RB*$LC9u%bh%*>>5E3kM{I-ALttC z?V+chmM>OrDXF-lduXKhXm=kx1@@cJ%(Nk<+rA=9wx~+r*>gg$J06Zc0B7{1VEZ}R z=+k7NgM$@gq+W8y=NwP}9T>F0+_|5=vO}lw>O){ z5eRO=&+U_u+5E}A=%}=q79HP_5aH;IOJ#SWuZJOkWjzcstyKEe6-ofiSz@%i{_%*S z|9HqYP=0z4(`rG$^3w$qi=}W8&-W0f9${a4P;(NVp!DlE?8L*|%E`%3$vM#~JKsg$ z=R7HDG7D$zo9HXfN=vJFzmGl_8X+7LeFvd4)u^Hgy}`UJVDM@86=xfy4K=I8XIE3t zpr)W79``_VK-F#x4*9IMd=ads&92GD?%(;& zcYL){RL)f@BQ3B67s}Up(fmespGUoKD}}h%XFW}C$9(0`j(Mi&Nj<})2}<)UkHh%Q<%K;}I)7Yhz`HOwb)Ymq3727Y zj9e+$+b-w}~sbL&+MbdDU^Ej zCgP|^G8JYN;z!s9ew4gUf3Pk9FKhv*bE5|?(9V~Qm-FKlz>@K_96H|?q|vDQgc}*h ze&i7#yT6sUCI<5a8r^jz&sb;;9Uu6;FIFQfi1Pt zEnU=D*1}1fp>OuI1aZf`I$x&)?vc{`R2$)2*cI%%3VBKxy_6|K>b``oLQYHu9g@F{ zbeKQX$+V69XPkXSnC^aJ<+MWPE9z#j!mQLt zqArfJu+gY1aW)4f|YGj2I z#p=-h3av;Lds3dK1pVbH&>#9D!;cdGi#*l9h;7vP1nJK}i=pa5xveuo!VzaoHH+D+ z&dDaM87!+29&So_EK9oFgs?>PU05a0~ml*$!3ddXuC<%0Ty&!!>q6wVA1HjeA{>dsvTqSdV+q;&VkB6gv@W zbST_PY>)h{Mf2z9K%viPJ8^z#mzJLG%FigeN@qHT0+N9)E5kX6aF|X`zMz!3YdX|p zZ`X#Ce!P_F#=;Sq7RL57^Uvhvr|M&?kQMdNPP)GH?W$v;xH2q7ujBd6x3sF@mJC;6 zuGh|N46dE4Ft<-`Tv-*=UTKw?EkigsofhI-xJcr}c|n#d(B0nLS*q@sSHZKPea=^$ zvbPqV2hGyf=6nL*`7>No*!K>bRvb3WoyCu+^NSfo=`PC(T_*lU6p=A6wjO>b^dA|aVnNw*N+4ExB^1CLJL)}kitLJJtRw1_y0P}PeGr@X2sWr&;-;nOBbG66D zU5!jxQ|qgvLUl^TK%i0N{>_zcqU{{q(at8Ckenl>@|Nu95YapunvO6M@SM6eC-Y30jW{68R=mr~X)Agx-Hz{gDvn0Eh%MPgar=WoXY= zg<|@|xsv>5n{dU)NLcvq+F89>LB7^zy@Y366nl_rJI30*^_6IF#mC$B7~eA}t95fLgKj*rAnruA~Y>qfY(6);_zC zoT)1j9c>zfyOQbVyojO`reS?+wMM!A#g$(0&meIQ@N4+sEM0|0;m_;l;d;gZy6(+f zpk&0wxjBBp9mm?mvoqT>c2A+!l$gK1V;a{6*uzNUrhvFkU}E>^-tm#$qjbuHZhare5$mkjM?JDw z_+v);1X7CCf6CpGwN)(c^%as3{?1gFDs;=ezK3a~DewNR z@0cH_lNFUjt>(H9Xx#_2?gP4|upS~XdGmO)oaIG<|88A3%1#@y3dLqc0h(9xe3PqJ zQ$*0V8?(8>ovwib!qJVNDxfq^$#l%XsF3Tj}a~Ae8)pDiy>?0N}CO8w<_#p zI>) zhn8vE6`4-!gEN#%aS%6R?WEC>0Wjj4=s1g z==jj!t}SG$>h(gtdq>;Vbdu&CLVtpQ?uwQ!WarMo@o^fwOwxa)Gj6lAOuw zD}TlC%+g1PaE?G@1nH%XT6wZTTVg^91%I-7>ADYi-3Pqx177z5Bdg`Q518)kS>aiQ zSA_b>b=nnbiWioec>WdHbE^m%6-XuD#XdM?#Z8j&(zMqpqrW^+!Q`3c^IIgOz`DAJP+`+(_$+zCN zmMXYz`(p-&I-sCF0T;QOXX>ASq7mGTnPzHKsddi5o-S?giLEm>dgX+Pj$U9!m&Phq z+S%sIcOaJKL%<)!N@_(tT#+Pt!YlSr)FnL;$8N1vt_9Hb|q~F*HogFu|vAGFj;C$ z&KCOV92f%5%LkGj2mJGFiZvLZZL%HANqd*;@4$ae2gOrIO5)D&siWWh(af zE#3R@(hu)B`^*ciyT7sY<2%kibIjb^6SW6}oT3^#JYwgzPbn~?*P74Y-Gv{6`77I7i z9jB0;W7Rqzs+g3siiJ%Yfq*~h%_v?Q6D>~{`s=gRW<^}1D#WvxyC71r!$ey+ym!9= zG=jV)<@sh}(E)Ge8sQs0x>at>$+a4t7GT2>E`U#vFt_cB$?r8uM@_*t4;=fdRdP@h zLAlcd4qz~2Re@P~tBc}x{pJojm%a;L!0o|Hn!)Khu~c)yKV(Q?**B zD9>%BeMu#=**4n*05D8yYjnPm%p|pSqc)3rF?xWpd>3gcr3a_OZm9pYC@ zf$6_M?6x*ZldD60XfmfIN-;LKKiil6mM^Y_h+aB+$MVr*OJBOX_4qehw>|mt7oR+H z>U8T{-)g=1aO?On`bpz~$2Ej}4n!kF!5x6E%^`y&!bcS1vNJ+lSMb2%-mB!qJ-0i& zt5!0nt2?YDSxl$nleIb>psdyL2n8>@rqg+eTGy_q)`vT-CD`;wb*&7-7!Rc6TPF|_ zj-4W)kf@#FpQzL^`U&86I=*!R;rfqMM+||q$=JPG8D!<$$_Z_u-ytev5zVL@8<|DO zF*S>VlJHm)?I)$6;_Eu z9m7?~?~dUrYIO=%i4C2upsdyD63SWziP~I4vG9gTw^>Ope8;tfEz@Z&F+k&`$smp3 zEuIdEtf>vJ4gKvD0B~ci+FWq*mt!IES*fKjS}#pa(dnES^w6*EcQ;c-iiUMDX6C2L ztfl+AQ`B~$i4|&Nc&pJrI;Nsmta0xLyGqg#2@(iM;$K5R4YGK-i;Bg@sG{C(KTpy| z9bpUCI=%eX6!LnszQ1&!RBTe&;tj&PcBLI?ya0Z zCT05_pC!v}mIWq?hVdhs#lP{j#w76(6D>EAIaHC`^2GzC z3SGK4-H+F@!^!GYLLH`il#Ng`8vW8miq+aM1(4iGtK&{|)28aQVFI@<-t+8+S8c#& z|99WGVZ&H+|EGR~z9)Fp@c08fj^S}X9#7)&JRbiI9{&uFe~riA;PIdEc-@=n@eVvL z#^Y){Hsi4qkH3#c1&`0+aRiTV;PE&fKfvRU@%SI{_)9$gS3Ld$9>4q+db|aXK0GeR z;|4so;qg0o%;IqXk1yfzH9Q{1<7qrz#N)rm<1g@7!sGv>NAlUl{{7ct@MH8yZeP58 z@z`(gqvLDI{M5#JZF1urrCZsEb=){vo!fY@nS731v~zSlVp7{ULzJtOx)0+raC59$?TRirfnt8x9)$CFbXvvrhkvk5t~UiZQQtV%chNcsmf_fMq;Yd`{~PI-n_j#B?yQ$jjTaW$f>TZMlY>quXU zA1hS~yQ)>4AilsLX3(!`5bt&1ki+vc;6)PQBJjLA3{UmCKL$_r`+Gcis@KGm6u$Ko zJg@6IJm2ra6Wl=psbJw1faaU=>!8T}u(MW0G<~&}4c2SIU{$X-#lWh5zt{t-di~Mf z1nX$m!Fp{PtRA!ke7$!8K);Jldx-3FvM8N^qSSJmo5Zn6i^!zoYr_CnuRjq3xca^5 z0bIQv?M;Axvg-if>H=Kn3=BYxPMt&{8bE-zxy7mBYUMy4>^~fay?XsyG1#l$FY#cn zUVpqdVL#b**k6|gdkruT?D};8;%~Bcw6b2Z3H>8sV5`^H#(=GUf4>K8_4=CL1olGL zfqkV5>~Z7sP_Sa&D)lA+;$O$PS~}>_7JNS%hOc`4u^4>S?*kru)oTcRX#n(;LUwD{ z;rn41zLa*YI<>h3$JV$5vw{o40FYll+Z={FsYE{1Yzp2KhOl}aHKEk+z6qsX_oxZ= zm99hBGoeN-lnM-D0f4@dyx??pbLLQ6Wc#r&P}S=zV`Qs-@AJr3y}qJ1$@bo^1GVUq zEqQY}qzWG70>FK{O#S}JGM!5pHMKik@&GRq!Ae z0Pb7hBiT-_5pvh=Z0@U+>$7=w(+yz=tJl}X$XWgVL64l(YiMXasA~OD*CG57mz?4I z^1&s%B>u|)tbbMKoXA$RJ|2dzdc89SU-kQ?9(>j7t9lc@|G4Y$-R8oV@9hHC<1*yH z$?Jj09h_5=8MjAD^;y)qfwuPbhKliMf9h4VWy;!7N?AX1iOc*U(w{27dk(jO0@>j) z_nQBsHrXjg#5ei|3Q;>@puiMN5njR-lYv4+h|#S~F2j@K_etRrh3~%>-kTW=1wq>C}7#@7om@U_fY%7osU$VdKNlQ^~#$wp@e8b70p(*7fnJn(T z^;)mRc`HU0|24B6jZ>*6^bcO`lsAMtV5m~)wXAt-N+=(Dr-*w2`l?i8I-*ibs$ULEs?UZc z)pU-eQq}xjXZ8AGC8>_Rh4PtTOd|Id;fWCWGB%@ELJL}57hiAwC0BeQPnljT!c@vx z#K0<;%IK7aZlNktqE&0V>q*T`EC*31N)swEPDJKiLIJV~e%@NGiKg*0+VmQ;Xo^xI z!&aeDqU>=BIi-v&PEv?!L;k$AR-Hq`{b-RziVGZ&YA5jk8Tos?NJYec5tR`8hZaV~ zrSqaU){eF|iVID;TvIlL8fZnYitbv!P~ zg3fh_RFjs7^UzTB6%{f4?v+EN!I1-xn|Bxi-HnnXapuD4*vLUgRn137^%{FM@bz2> z66aNK>vAyLr~aHQ_$Wq(plr|**e6+@0ye3W66oxp#>!DtHjNEaj%Mj>MMRZPFWhe|eMqPIvMia0LIj`fUedvOUO!u9K{Yl@7Xsw6wB zbxO9{K&H~^X^Pyh+t%%nDFvQfJIXT(A;g=>qgA~Cl4i~11483s8Psg9Bhh5*{+mOy z2f{RaFo$MUHJ@hH>q{>nntfxpY4+GZjS_4Sz=mQaoC^|BjM`XHx6^zQ$1b=Ts}Rp+ z0#cOFMQY?FOmTpI-E`mh@V4QhiM`tfhi=+CzI$|n66iYp$eY+Sp$2gEeN*fjYZpQF z$a4_&b74gN{TxJ9)qF%%uMvBDp$M1vbRAKD9!1nNbaSxOKfcFg%xdt|-az1Wkc*&y zFQ8{hJk7z&|0;}^U&_HtRqY*~Ls-2wO3$xrGh3>Dy=7ou+HH*d6wY|*EfKDpM1iT$ zlJiAgPuZAc1*>l&8GBmcmPwM8wM*tM?#^T=jB{)@?dj$w-DFMhWBF}w9Na#ytb*=U-B~4_d=2H#1^dUO$JHH5pDZW0^rxnR zQQ||k&$MP(;UUU$wxs1y>3REQ$I6p8$JWRxTdwb^&W9GrDMc%9%=?c zU&!J4s@j`8O-Q}gXN8ab3TZ+}VBcF>(C)6Y`ANp+MS}l0{4|5)N>68;9LhX#Zwh9d zfR;2mJ(eU>cDE>X?Skar)`zIFqktfHh^vcAmFu*0fN~>oPXDAt1yB)w!~+zxMF=S{ znXV|&YMRG&nHv(L_1mONxQ6Ez8kAOsuL1@n8>G#7DHt#;@vXRZbZm5dgz}r%d7ymU zABw8N=~`TXj2R+#=!{(?aB7rbn|o<-q_s12gippo>h&|rN6VI)HTB6QG8kQ9itf0Z z7vE@aUauyXO!B^-etxmMZrHgj$uz`PXye9$)H|0?Jhb%GW38u;Egd<2=F}Z$P90wQ z{v%6YJw?}4w2t5P?_WH&bo9=%-+Aoi`yO9<>TzhRi_iVl;S*Ho%;6D15Yg9|E1Q~?`)l3q?a$>cf57v`M*B=hej%zp-u98>M42$_mlC-8$i35 zW9rjdX<^SMG(#Zs;}d8HRz&tnMk_!r_@xAXgBBV02%6nc7xJP07+P~c)P;(`abg9d zp>5f5aZ64N6+;8keFprL?<_SNRVWght7IlgENn+iyc84@+kfpXfF zR8CBGT~56F?^ts{$cY&ERxc%Pp`0E`K{9H_+H?*n$yIl58~pU>*o{6HHrz}_;ery0 zp&9;!zxBn?=A}!>8fPWvl-G2z)<^;fJ=O0S#XZ@n3<|~tbeojCD)$OBh@Xqxjt9$W zxq~Ks_KtTZ4t!V*7HqGBCr5JY(vRX_Y!s?IDw~t zd46LJiV*eSH|q`7C1Ygkj8m(h>K;%W5vWWf+58aMjpZo{^CAk8J`C@@Lz1qxh*_rE zAy%}rmeh$T!)(E)V5A@Y#iQXH2ghz~gNoCuCzRSa5^A7>{2c)6AMo!g3EM&>fZ6Ai zw6SXOxpz+75(;Tt2TLZWj@-D3cW4(PwbX%?={cI04l!|>vyuoM1m4oZ0UXGs!}sEk zXykKIcw1Cu8Jj2=+CYJEQ*RY$!<0IhIYhS?Qy+AP@09+M3e2WFoZ=uqQo_Q$)Xzud zHS2SXWW^FcQ_O_XD+PH@XlyAuk@9_JpE|R3y?U@qt8;WkCV3^K9F+M%Vdlzj%(a*0 ztHjWwjKgIb9th%EfCPqDye*Y;b@URFk5)FyZvg|ZKQ$AnoC_n5#SLe>1-HLm8FpTd zzU{xBWAK@Zp2cV0U!=E#deb%dd)+qqj(tyW|44h!j{s-T)HWZ3stm{DMv~$PpkAqj z5J6E6b(yV~Vf9R#o)SS0s$Ky7irMJrak?BN{JRk(d`lh@>WV%R>i0d4gn!U=B>dqD zkkEszjtph$zqqOo2rMvB7N89R#bH@N@bMHPY1yW5+-mAkUikjlK?g=eIVkwH2nt@A zhl0AIkAnKWiOlOwK6q!hQSehQ`CBAy08?{B&H$wiC&V9^-~@c;QmN2p>qRn4dZvfV zm&VM`Baw+5UQAgcTaz(f)+hIbg-Z;{gX{rkHfF;5AUhPEslxC7KJPi^D9e~YChM26 z2sj=jH8W#Q*V8^CHX$rZ*c-%WK=8=g)D{|guxr*xU>}wJ3XtoiG`c8>FY|q^Z3wmo z3hXh1;v5(g`oQqu6q1^olXxZZCmgbFf&gg($-RA=wJUu~Uw-NLJubh#)phyxl~;RPH!bhR*4IQ4E7P6BLMh{Jhg2 zislPrb!ADWFh)!G#54lMgBjP7<$dR}HK*~rn1*s(VDu7R%6(&xMcMOE@iS1Sxt)XK z7xrFuQ1A#c25JUfTSZnO<(j!yok3f&6c=#oti^A6h)vLIWvqKwxMscT-N%pMzZtu0rOq8dIS z6bP4e7Tw`UY~iCUX(T3c9FBxP9P!?%PWZbK{xqM*pL9i^Kk4^9t`q*G>-=eJSSL*D z7!2T=8Un_E(d&GpAmCDyErNkmo3%+1lxa4^*+D8sdd7TJ8i1iMhRQM|QSNXhB7~Y6 zC33-q18P?Cwe&aFjXqLDoSnmt%m%*8DWt@`p>T`1#zM9pOTQ=WSxOC~x=wQ!HN7

    B>uT-_kbKxuyIFShdA}w;TY~+hM zpwGa9qMoWvm!?ip(hJ?EWFV?%6Vac={|Be(7BaQ4d&-U3YLoJINYSW=?v21IVHi(_ z14;*YwBJQw=izE)U%5IqBcuQUz3UG z0^<+p(Q*?fK+QWOz;$C`gV!N$2}%Uebx!C9BZPiy9--@sKB4RPSNHaA_y5#&LjP~W zgx)sXE!|uAbH`XZ=?5WVD(#Q%PPQR2H;Ax3!VXqV$VA^n?I4zONW)4ZdyXZCXz_(I-^>zQ-lQ5K5R%n7cHG?&a=0 CSsW9Q6AhCx`#sb#mC~JLI#WYsn!5 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 delta 22 ecmbQfO|t(6d&3k)9_HV#8Mg8>{(H?-ss{jVehD%F diff --git a/.serena/memories/blood_battle_scoring_v1.md b/.serena/memories/blood_battle_scoring_v1.md index 9b403b0..f9eed6c 100644 --- a/.serena/memories/blood_battle_scoring_v1.md +++ b/.serena/memories/blood_battle_scoring_v1.md @@ -2,15 +2,16 @@ - 已从工程占位分切换到“最小可扩展正式版”计分骨架。 - 当前已支持的规则主线包括:七对、对对胡、金钩钓、清一色、根、抢杠胡、杠上花、杠上炮、海底捞月、海底炮、明杠/补杠/暗杠、退税、查叫、一炮多响、最小正式版过水不胡。 - 当前胡牌计分口径:`paymentScore = 1 << totalFan`。 -- 一炮多响:只对 `HU` 开放多赢家同窗裁决,`PENG/GANG` 仍单赢家。 +- 一炮多响:只对 `HU` 开放多赢家同窗裁决,`PENG / GANG` 仍单赢家。 - 过水不胡:玩家在响应窗口里本可 `HU` 但选择 `PASS` 后,直到自己下一次真正摸牌前,不能再做响应胡;不影响碰、杠、自摸胡。 -- 后端最小验证:`cd backend && mvn test` 通过,当前 53 个测试通过。 -- 前端 H5 对局页已经完成两轮联调: - - 已区分“当前回合动作”和“响应动作”两种动作面板。 - - `DISCARD` 继续通过点击手牌执行,`GANG/HU` 等额外动作走统一面板。 - - 响应动作面板会展示 `sourceSeatNo`、`triggerTile`、`triggerEventType`、`windowId`。 - - 已支持在对局页切换玩家视角,并自动刷新对应 `GameStateResponse` 与重连该玩家私有 WebSocket 主题。 - - 公共事件接收已改为统一入口处理,会在 `RESPONSE_WINDOW_CLOSED`、`TURN_SWITCHED`、`TILE_DISCARDED`、`GAME_PHASE_CHANGED` 等场景下清理已失效的私有动作面板,避免旧窗口残留。 - - 公共事件时间线已支持中文摘要文案、时间展示和原始载荷折叠查看,便于联调时同时看“业务含义”和“真实 payload”。 -- 注释约定继续有效:后端复杂规则、前端复杂交互和后续数据库脚本都要补适当偏多的中文注释。 -- 前端验证:`cd frontend && npm run build` 通过。 \ No newline at end of file +- 教学建议链路已扩展为:`recommendedAction`、`explanation`、`candidates`,其中 `candidates` 透传到私有 WebSocket 教学消息,前端已支持展示候选牌、评分和原因标签中文映射。 +- 前端 H5 对局页已完成正式动作面板、响应动作面板、玩家视角切换、公共事件时间线、最近结算卡片、教学推荐高亮等联调。 +- 2026-03-20 当日新增一轮前端结构收口: + - 新增 `docs/H5_GAME_PAGE_ARCHITECTURE.md`,完成 `S1-08` 页面信息架构与拆分方案。 + - 前端共享类型已抽到 `frontend/src/types/game.ts`。 + - UI 标签映射与事件/结算格式化函数已抽到 `frontend/src/utils/gameUi.ts`。 + - 已拆出展示组件:`GameActionDock.vue`、`GameMessageStack.vue`、`PublicEventTimeline.vue`。 + - `App.vue` 当前定位已经收敛为“页面容器 + 请求/订阅协调层”,便于下一轮继续拆 `RoomPage / GamePage / ReviewPage`。 +- 2026-03-20 当日还修复了 `GameSessionServiceTest` 中两条“过水不胡”测试的脆弱构造,改为显式控制响应胡候选与后续安全弃牌,避免依赖随机初始牌与中间牌路。 +- 当前验证状态:`cd backend && mvn test` 通过;`cd frontend && npm run build` 通过。 +- 注释约定继续有效:后端复杂规则、前端复杂交互、后续数据库脚本和迁移都要补适当偏多的中文注释,尤其说明规则判断、状态切换、消息边界与字段语义。 \ No newline at end of file diff --git a/backend/src/main/java/com/xuezhanmaster/game/service/GameSessionService.java b/backend/src/main/java/com/xuezhanmaster/game/service/GameSessionService.java index 5523a40..491925f 100644 --- a/backend/src/main/java/com/xuezhanmaster/game/service/GameSessionService.java +++ b/backend/src/main/java/com/xuezhanmaster/game/service/GameSessionService.java @@ -301,7 +301,8 @@ public class GameSessionService { currentSeat.getPlayerId(), advice.teachingMode(), advice.recommendedAction(), - advice.explanation() + advice.explanation(), + advice.candidates() ); } diff --git a/backend/src/main/java/com/xuezhanmaster/ws/dto/PrivateTeachingMessage.java b/backend/src/main/java/com/xuezhanmaster/ws/dto/PrivateTeachingMessage.java index a31ebc7..766f520 100644 --- a/backend/src/main/java/com/xuezhanmaster/ws/dto/PrivateTeachingMessage.java +++ b/backend/src/main/java/com/xuezhanmaster/ws/dto/PrivateTeachingMessage.java @@ -1,11 +1,15 @@ package com.xuezhanmaster.ws.dto; +import com.xuezhanmaster.teaching.dto.CandidateAdviceItem; + +import java.util.List; + public record PrivateTeachingMessage( String gameId, String userId, String teachingMode, String recommendedAction, - String explanation + String explanation, + List candidates ) { } - diff --git a/backend/src/main/java/com/xuezhanmaster/ws/service/GameMessagePublisher.java b/backend/src/main/java/com/xuezhanmaster/ws/service/GameMessagePublisher.java index 5cbffc0..c77a9c9 100644 --- a/backend/src/main/java/com/xuezhanmaster/ws/service/GameMessagePublisher.java +++ b/backend/src/main/java/com/xuezhanmaster/ws/service/GameMessagePublisher.java @@ -3,6 +3,7 @@ package com.xuezhanmaster.ws.service; import com.xuezhanmaster.game.domain.ResponseActionSeatCandidate; import com.xuezhanmaster.game.domain.ResponseActionWindow; import com.xuezhanmaster.game.event.GameEvent; +import com.xuezhanmaster.teaching.dto.CandidateAdviceItem; import com.xuezhanmaster.ws.dto.PrivateActionCandidate; import com.xuezhanmaster.ws.dto.PrivateActionMessage; import com.xuezhanmaster.ws.dto.PrivateTeachingMessage; @@ -97,10 +98,18 @@ public class GameMessagePublisher { ); } - public void publishPrivateTeaching(String gameId, String userId, String teachingMode, String recommendedAction, String explanation) { + public void publishPrivateTeaching( + String gameId, + String userId, + String teachingMode, + String recommendedAction, + String explanation, + List candidates + ) { messagingTemplate.convertAndSend( "/topic/users/" + userId + "/teaching", - new PrivateTeachingMessage(gameId, userId, teachingMode, recommendedAction, explanation) + // 教学消息需要同时携带推荐结果与备选列表,前端才能把“为什么是这张牌”解释完整。 + new PrivateTeachingMessage(gameId, userId, teachingMode, recommendedAction, explanation, candidates) ); } diff --git a/backend/src/test/java/com/xuezhanmaster/game/service/GameSessionServiceTest.java b/backend/src/test/java/com/xuezhanmaster/game/service/GameSessionServiceTest.java index abdf656..e7976ef 100644 --- a/backend/src/test/java/com/xuezhanmaster/game/service/GameSessionServiceTest.java +++ b/backend/src/test/java/com/xuezhanmaster/game/service/GameSessionServiceTest.java @@ -786,8 +786,9 @@ class GameSessionServiceTest { prepareWinningHand(winnerSeat); session.getTable().setCurrentSeatNo(2); ensureSeatHasMatchingTiles(session.getTable().getSeats().get(2), "9筒", 1); - ensureSeatHasMatchingTiles(session.getTable().getSeats().get(3), "9筒", 1); - removeMatchingTilesFromOtherSeats(session, "9筒", 1, 2, 3); + // 这里只需要保证 seat 1 能对 seat 2 的 9筒形成“可胡后选择 PASS”的场景。 + // 不再额外给 seat 3 补 9筒,避免它也意外成为同一响应窗里的第二个胡牌候选,导致测试依赖随机手牌分布。 + removeMatchingTilesFromOtherSeats(session, "9筒", 1, 2); gameSessionService.performAction( started.gameId(), @@ -801,9 +802,12 @@ class GameSessionServiceTest { assertThat(winnerSeat.isPassedHuBlocked()).isTrue(); assertThat(afterPass.currentSeatNo()).isEqualTo(3); + // 这里取 seat 3 当前手里的安全牌继续推进牌局,只验证“未摸到自己下一张牌前仍然不能响应胡”。 + String seatThreeSafeDiscard = session.getTable().getSeats().get(3).getHandTiles().get(0).getDisplayName(); + removeMatchingTilesFromOtherSeats(session, seatThreeSafeDiscard, 3); GameStateResponse afterSecondDiscard = gameSessionService.performAction( started.gameId(), - new GameActionRequest("player-4", "DISCARD", "9筒", null) + new GameActionRequest("player-4", "DISCARD", seatThreeSafeDiscard, null) ); assertThat(session.getPendingResponseActionWindow()).isNull(); @@ -863,12 +867,15 @@ class GameSessionServiceTest { assertThat(afterSeatZeroDiscard.currentSeatNo()).isEqualTo(1); assertThat(winnerSeat.isPassedHuBlocked()).isFalse(); - removeMatchingTilesFromOtherSeats(session, "8万", 1); + // 解锁完成后,重新稳定构造一个“seat 2 打出 9筒,seat 1 可以响应胡”的窗口, + // 避免继续依赖中间多轮摸打与随机初始牌造成的测试波动。 + prepareWinningHand(winnerSeat); + GameSeat sourceSeat = session.getTable().getSeats().get(2); + sourceSeat.getHandTiles().clear(); + sourceSeat.receiveTile(parseTile("9筒")); + session.getTable().setCurrentSeatNo(2); + removeMatchingTilesFromOtherSeats(session, "9筒", 1, 2); - gameSessionService.performAction( - started.gameId(), - new GameActionRequest("player-2", "DISCARD", "8万", null) - ); GameStateResponse afterHu = gameSessionService.performAction( started.gameId(), new GameActionRequest("player-3", "DISCARD", "9筒", null) diff --git a/backend/src/test/java/com/xuezhanmaster/ws/service/GameMessagePublisherTest.java b/backend/src/test/java/com/xuezhanmaster/ws/service/GameMessagePublisherTest.java index aacf6f0..500078d 100644 --- a/backend/src/test/java/com/xuezhanmaster/ws/service/GameMessagePublisherTest.java +++ b/backend/src/test/java/com/xuezhanmaster/ws/service/GameMessagePublisherTest.java @@ -4,8 +4,10 @@ import com.xuezhanmaster.game.domain.ActionType; import com.xuezhanmaster.game.domain.ResponseActionOption; import com.xuezhanmaster.game.domain.ResponseActionSeatCandidate; import com.xuezhanmaster.game.domain.ResponseActionWindow; +import com.xuezhanmaster.teaching.dto.CandidateAdviceItem; import com.xuezhanmaster.ws.dto.PrivateActionCandidate; import com.xuezhanmaster.ws.dto.PrivateActionMessage; +import com.xuezhanmaster.ws.dto.PrivateTeachingMessage; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.messaging.simp.SimpMessagingTemplate; @@ -84,4 +86,29 @@ class GameMessagePublisherTest { .extracting(candidate -> candidate.actionType()) .containsExactly("PENG", "PASS"); } + + @Test + void shouldPublishStructuredTeachingMessageWithCandidateList() { + gameMessagePublisher.publishPrivateTeaching( + "game-1", + "user-1", + "BRIEF", + "9筒", + "建议先打孤张。", + List.of( + new CandidateAdviceItem("9筒", 90, List.of("ISOLATED_TILE")), + new CandidateAdviceItem("1万", 70, List.of("EDGE_TILE")) + ) + ); + + ArgumentCaptor captor = ArgumentCaptor.forClass(PrivateTeachingMessage.class); + verify(messagingTemplate).convertAndSend(org.mockito.ArgumentMatchers.eq("/topic/users/user-1/teaching"), captor.capture()); + + PrivateTeachingMessage message = captor.getValue(); + assertThat(message.teachingMode()).isEqualTo("BRIEF"); + assertThat(message.recommendedAction()).isEqualTo("9筒"); + assertThat(message.candidates()).hasSize(2); + assertThat(message.candidates().get(0).tile()).isEqualTo("9筒"); + assertThat(message.candidates().get(0).reasonTags()).containsExactly("ISOLATED_TILE"); + } } diff --git a/docs/DEVELOPMENT_PLAN.md b/docs/DEVELOPMENT_PLAN.md index 059e9e6..68213e4 100644 --- a/docs/DEVELOPMENT_PLAN.md +++ b/docs/DEVELOPMENT_PLAN.md @@ -278,12 +278,21 @@ ws WebSocket 配置与消息发布 ### 6.2 当前前端结构 -当前前端仍以单页面原型为主,但已经承担三类职责: +当前前端仍以 `App.vue` 作为页面容器,但已经从“单文件原型”开始向“可继续拆页的正式骨架”演进,当前承担三类职责: - H5 房间流操作 - 对局状态展示 - WebSocket 消息接收与展示 +同时,已完成第一轮最小拆分准备: + +- 共享类型:`frontend/src/types/game.ts` +- UI 格式化工具:`frontend/src/utils/gameUi.ts` +- 展示组件: + - `frontend/src/components/GameActionDock.vue` + - `frontend/src/components/GameMessageStack.vue` + - `frontend/src/components/PublicEventTimeline.vue` + 后续建议按页面拆分为: - `HomePage` @@ -337,11 +346,15 @@ ws WebSocket 配置与消息发布 - WebSocket 私有动作订阅 - WebSocket 私有教学订阅 - `/api` 与 `/ws` 代理配置 +- 正式动作面板与响应动作面板联调 +- 公共事件时间线与最近结算卡片 +- 教学推荐高亮与候选建议列表 +- 对局页信息架构文档与最小组件拆分基础 ### 7.3 当前进行中 - 动作系统从“定缺 + 出牌”扩展到真正的麻将动作系统 -- H5 原型页向正式对局页演进的规划和拆解 +- H5 页面容器继续向 `RoomPage / GamePage / ReviewPage` 拆分 ### 7.4 当前已完成的文档治理 diff --git a/docs/H5_GAME_PAGE_ARCHITECTURE.md b/docs/H5_GAME_PAGE_ARCHITECTURE.md new file mode 100644 index 0000000..a684ae8 --- /dev/null +++ b/docs/H5_GAME_PAGE_ARCHITECTURE.md @@ -0,0 +1,321 @@ +# H5 对局页信息架构与页面拆分方案 + +当前状态快照日期:`2026-03-20` + +--- + +## 1. 文档目标 + +这份文档用于落实 `S1-08`,为 H5 原型页向正式页面演进提供可以直接执行的拆分输入。 + +它要解决的不是“最终视觉长什么样”,而是先把下面几件事固定下来: + +- 页面边界怎么切 +- 正式对局页内部区域怎么分 +- 公共消息和私有消息分别落在哪个区 +- 哪些状态应该留在页面容器,哪些应该沉到子组件 +- 下一轮前端继续拆页时,如何避免再次把逻辑堆回 `App.vue` + +这符合: + +- `KISS`:先拆职责边界,不先追求完整视觉重写 +- `YAGNI`:当前不引入路由级复杂壳层和状态库 +- `SOLID`:页面容器、消息面板、动作面板、公共时间线分层明确 +- `DRY`:共享类型与格式化函数抽离,避免多组件重复维护 + +--- + +## 2. 当前现状判断 + +当前 `frontend/src/App.vue` 已经承担了四类职责: + +1. 房间流操作 +2. 对局页状态与动作提交 +3. WebSocket 订阅与消息清理 +4. 消息卡片、结算卡片、时间线等展示 + +这说明它已经不再是“临时原型脚本”,而是一个事实上的页面容器。 + +当前如果继续在 `App.vue` 里叠加: + +- 断线重连提示 +- 教学开关 +- 复盘入口 +- 页面级切换 +- 更多正式规则结算展示 + +后续维护成本会快速上升。 + +--- + +## 3. 正式页面拆分建议 + +### 3.1 页面层级 + +建议前端逐步演进为以下页面: + +- `HomePage` + - 负责产品入口、快速建房、邀请码输入、最近房间入口 +- `RoomPage` + - 负责建房后、开局前的房间管理与玩家准备 +- `GamePage` + - 负责正式对局中的牌桌、动作、教学、公共事件 +- `ReviewPage` + - 负责局后个人复盘、关键失误与建议回看 + +当前阶段不要求一次性引入完整路由,但组件组织和文档命名要按这个终态准备。 + +### 3.2 当前这一轮的落地点 + +本轮先聚焦 `GamePage`。 + +原因: + +- 房间流已经基本够用 +- 当前复杂度主要集中在对局页 +- 动作消息、教学消息、结算时间线都已经真实接入 +- 正式血战计分规则后续迭代也主要会继续压在对局页 + +--- + +## 4. GamePage 信息分区 + +### 4.1 顶部概览区 + +职责: + +- 显示对局阶段 +- 显示当前视角 +- 显示剩余牌墙 +- 显示当前系统提示 + +说明: + +- 顶部概览区只做“当前局势总览” +- 不承载具体动作按钮 +- 不放长文教学解释,避免遮挡主桌面 + +### 4.2 自己手牌与当前动作区 + +职责: + +- 展示自己的手牌与副露 +- 承接定缺、主动回合动作 +- 处理“点击手牌出牌”的高频交互 +- 在手牌区局部高亮教学推荐牌 + +说明: + +- 出牌仍保留“点击手牌直接触发” +- 杠、自摸胡等低频动作通过统一动作面板承接 +- 这是为了保留 H5 的高频单手操作效率 + +### 4.3 响应动作区 + +职责: + +- 响应窗口出现时展示 `PENG / GANG / HU / PASS` +- 清晰显示来源座位、目标牌、触发事件类型 + +说明: + +- 该区域与“当前回合动作区”共用一个动作容器 +- 但视觉上必须区分当前回合与响应窗口 +- 当前项目已经实现统一动作消息结构,因此前端只需按 `actionScope` 切换视图 + +### 4.4 私有教学区 + +职责: + +- 展示推荐动作 +- 展示解释文案 +- 展示候选牌、评分、原因标签 + +说明: + +- 私有教学区必须与公共区域显著分离 +- 教学候选列表属于私有信息,不得混入公共事件流 +- 移动端下建议紧贴动作区,但与牌桌保持卡片边界 + +### 4.5 公共桌面区 + +职责: + +- 展示其他座位的公开信息 +- 展示弃牌、副露、积分、是否已胡 + +说明: + +- 公共桌面区不显示其他玩家私有教学 +- 这是消息边界在 UI 层的直接体现 + +### 4.6 公共事件与最近结算区 + +职责: + +- 展示公共事件时间线 +- 展示最近一次结算卡片 +- 展示最近结算对应的分数变化 + +说明: + +- 公共事件是调试和正式观感都需要保留的区域 +- 但在正式页面中,它应当是“次主区”,不是最上方首屏主区 + +--- + +## 5. 组件拆分建议 + +### 5.1 页面容器组件 + +- `GamePageContainer` + - 负责请求、WebSocket、页面级状态聚合 + - 负责“谁是当前视角用户”和“当前牌局状态”这两个页面主状态 + +当前阶段由 `App.vue` 暂代这个角色。 + +### 5.2 已建议先拆出的展示组件 + +本轮已经先按最小风险拆出三块: + +- `GameActionDock.vue` + - 只负责主动动作 / 响应动作展示与按钮提交 +- `GameMessageStack.vue` + - 只负责私有动作摘要卡与私有教学卡 +- `PublicEventTimeline.vue` + - 只负责最近结算、分数变化与公共事件时间线 + +这样做的目的不是“拆得越碎越好”,而是先把最容易继续膨胀的三个展示区从页面容器中拔出来。 + +### 5.3 下一轮建议继续拆的组件 + +- `RoomControlPanel` + - 建房、入房、准备、开局入口 +- `SelfHandPanel` + - 手牌、副露、推荐牌高亮 +- `PublicSeatBoard` + - 其他玩家公开桌面 +- `ViewSwitchPanel` + - 多玩家视角切换 + +--- + +## 6. 状态归属建议 + +### 6.1 必须保留在页面容器的状态 + +- `room` +- `game` +- `currentUserId` +- `publicEvents` +- `privateAction` +- `privateTeaching` +- `wsStatus` +- `busy` +- `error` + +原因: + +- 它们同时被多个区域消费 +- 其中 `privateAction` 与公共事件清理之间有联动 +- `currentUserId` 变化会触发重拉状态与重连私有 WebSocket + +### 6.2 可下沉为共享工具的内容 + +- 接口响应类型 +- 私有/公共消息 DTO +- UI 标签映射 +- 事件摘要格式化函数 +- 结算详情解析函数 + +本轮已经落地为: + +- `frontend/src/types/game.ts` +- `frontend/src/utils/gameUi.ts` + +### 6.3 当前不建议引入的东西 + +- Pinia 等全局状态库 +- 过早的页面级缓存层 +- 复杂消息归档服务 + +原因: + +- 当前页面仍是单条主链,复杂状态库会提升理解成本 +- 现阶段优先保证规则联调和 H5 可操作性 + +--- + +## 7. 移动端布局建议 + +### 7.1 布局优先级 + +移动端从上到下建议为: + +1. 顶部提示与对局摘要 +2. 自己手牌区 +3. 动作区 +4. 私有教学区 +5. 公共桌面区 +6. 公共事件时间线 + +### 7.2 关键原因 + +- 手牌和动作区必须靠前,保证高频操作路径最短 +- 私有教学紧跟动作区,减少视线切换 +- 公共事件时间线放在靠后位置,避免压缩手牌点击区域 + +### 7.3 H5 交互要求回落 + +- 关键按钮保持大点击热区 +- 不依赖 hover +- 响应窗口出现时动作按钮必须明显聚焦 +- 私有教学不能遮住手牌 + +--- + +## 8. 本轮已经落地的最小结构调整 + +为降低后续继续拆页的成本,本轮已经完成以下基础整理: + +- 抽离共享类型文件,减少组件间类型重复 +- 抽离 UI 格式化工具,减少多处重复的事件/结算渲染逻辑 +- 抽离动作面板组件 +- 抽离私有消息栈组件 +- 抽离公共事件时间线组件 + +这一步仍然保持: + +- `App.vue` 作为页面容器 +- 行为逻辑不变 +- WebSocket 订阅逻辑不变 +- 动作提交链路不变 + +--- + +## 9. 下一轮前端任务建议 + +建议按以下顺序继续: + +1. 把 `App.vue` 演进为 `GamePageContainer` +2. 把房间流区域抽成 `RoomPage` 或 `RoomControlPanel` +3. 把手牌区和公共桌面区拆成独立组件 +4. 再决定是否引入路由和页面级导航 + +如果下一轮要优先做体验而不是结构,建议优先加: + +- 教学开关入口 +- WebSocket 断线提示 +- 对局结束后的复盘入口占位 + +--- + +## 10. 验收结论 + +`S1-08` 完成的标准不是“页面完全重写”,而是: + +- 正式对局页的职责边界已经明确 +- 私有区、公共区、动作区层级已经固定 +- 下一轮前端可以按文档和当前代码骨架继续拆,不必重新讨论页面结构 + +当前结论:已满足。 diff --git a/docs/SPRINT_01_ISSUES_BOARD.md b/docs/SPRINT_01_ISSUES_BOARD.md index c8f3a1a..ee9501b 100644 --- a/docs/SPRINT_01_ISSUES_BOARD.md +++ b/docs/SPRINT_01_ISSUES_BOARD.md @@ -44,52 +44,7 @@ Sprint 目标: ## 2. 待做 -### S1-08 [H5] 对局页信息架构与页面拆分方案 - -## 背景 - -当前 `App.vue` 是原型操作台,已经能跑主流程,但信息和状态都堆在单页里。后续如果不先定义页面结构,动作系统一扩展,前端会迅速失控。 - -## 目标 - -输出 H5 正式对局页拆分方案,明确: - -- 房间页、对局页、复盘页的职责 -- 对局页内的信息分区 -- 私有教学面板和动作面板层级 -- 公共事件区与私有区边界 - -## 范围 - -- 页面职责定义 -- 组件拆分建议 -- 状态归属建议 -- 移动端布局区块说明 - -## 非范围 - -- 不在本任务里实现完整 UI -- 不在本任务里处理所有视觉细节 - -## 依赖 - -- `S1-05` - -## 产出物 - -- 页面拆分文档 -- 信息架构草图说明 -- 下一轮前端任务拆分建议 - -## 验收标准 - -- 下一轮前端改造可以按本文档直接开始 -- 公共区、私有区、动作区职责清楚 - -## 验证方式 - -- 文档评审 -- 与当前 H5 要求、周计划和阶段看板一致 +当前无新增待做项。下一轮若继续前端方向,建议从 `GamePageContainer / RoomPage` 拆分继续推进。 --- @@ -293,6 +248,35 @@ Sprint 目标: - 当前定缺和出牌流程未被破坏 - `npm run build` 已通过 +### S1-08 [H5] 对局页信息架构与页面拆分方案 + +## 已完成内容 + +- 新增文档 `docs/H5_GAME_PAGE_ARCHITECTURE.md` +- 明确了 `HomePage / RoomPage / GamePage / ReviewPage` 的职责边界 +- 明确了正式对局页内的六个信息分区: + - 顶部概览区 + - 自己手牌与当前动作区 + - 响应动作区 + - 私有教学区 + - 公共桌面区 + - 公共事件与最近结算区 +- 明确了页面容器与子组件的状态归属边界 +- 前端已完成最小拆分准备: + - 共享类型抽到 `frontend/src/types/game.ts` + - UI 格式化工具抽到 `frontend/src/utils/gameUi.ts` + - 展示组件拆出: + - `GameActionDock.vue` + - `GameMessageStack.vue` + - `PublicEventTimeline.vue` +- 当前重构保持原有动作提交流程、WebSocket 订阅逻辑和 H5 操作路径不变 + +## 验收结果 + +- 下一轮前端可以按文档与当前骨架继续拆页 +- 公共区、私有区、动作区职责已经固定 +- `npm run build` 已通过 + ### S1-06 [研究] 响应优先级裁决规则澄清 ## 已完成内容 diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 3e4a048..43955d2 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,105 +1,31 @@