From 78b570bd75b731735824a5c59e7f1ee5b21e3d3f Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 7 Jan 2026 14:33:10 +0100 Subject: [PATCH 1/3] Hint for first part --- assets/game1.js | 62 ++++++++++++++++++++++- assets/styles/game1.css | 32 ++++++++++-- public/audio/message.mp3 | Bin 0 -> 14744 bytes src/Game/Service/GameResponseService.php | 57 +++++++++++++++++++-- templates/game/index.html.twig | 6 ++- 5 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 public/audio/message.mp3 diff --git a/assets/game1.js b/assets/game1.js index c6d93f8..4141f60 100644 --- a/assets/game1.js +++ b/assets/game1.js @@ -1,6 +1,9 @@ /* Game1 entry point built with Webpack Encore */ import './styles/game1.css'; +let sequenceFinished = false; +let stillPlayingSound = true; + function subscribeToMercure(mercurePublicUrl, topic) { try { const url = mercurePublicUrl + '?topic=' + encodeURIComponent(topic); @@ -10,6 +13,25 @@ function subscribeToMercure(mercurePublicUrl, topic) { try { const data = JSON.parse(event.data); console.log('[Mercure][game1] Update:', data); + + // data is [sendTo, message] + if (Array.isArray(data) && data.length >= 2) { + const messageContainer = document.getElementById('message-container'); + if (messageContainer) { + const msgEl = document.createElement('div'); + msgEl.className = 'message'; + msgEl.textContent = data[1]; + msgEl.style.color = '#0F0'; // Green for incoming messages + msgEl.style.marginBottom = '10px'; + messageContainer.appendChild(msgEl); + if(stillPlayingSound) + playSound(); + console.log('[Mercure][game1] sequenceFinished status:', sequenceFinished); + if (sequenceFinished) { + flashRed(); + } + } + } } catch (e) { console.log('[Mercure][game1] Raw event:', event.data); } @@ -25,6 +47,28 @@ function subscribeToMercure(mercurePublicUrl, topic) { } } +function playSound() { + const sound = document.getElementById('message-sound'); + if (sound) { + sound.currentTime = 0; + sound.play().catch(e => console.warn('[Audio] Playback failed:', e)); + } +} + +function flashRed() { + console.log('[Game1] Triggering flashRed'); + const body = document.body; + body.classList.remove('flash-red'); + void body.offsetWidth; // Trigger reflow to restart animation + body.classList.add('flash-red'); + + // Also remove it after animation finishes so it's clean for inspection + setTimeout(() => { + body.classList.remove('flash-red'); + console.log('[Game1] Removed flash-red class'); + }, 150); +} + async function fetchJson(url, options = {}) { const opts = { ...options }; const headers = new Headers(opts.headers || {}); @@ -125,16 +169,26 @@ document.addEventListener('DOMContentLoaded', async () => { const printNextMessage = () => { if (currentMessageIndex < messages.length) { + const msg = messages[currentMessageIndex]; const msgEl = document.createElement('div'); - msgEl.className = 'message'; + + let extraClass = ''; + if(msg[2]) + extraClass = msg[2]; + + msgEl.className = 'message ' + extraClass; msgEl.textContent = msg[0]; - msgEl.style.color = '#F00'; msgEl.style.marginBottom = '10px'; messageContainer.appendChild(msgEl); + playSound(); + currentMessageIndex++; setTimeout(printNextMessage, msg[1]); + if (sequenceFinished) { + flashRed(); + } } else { // After it has printed a set of messages, it has to start a timer of 2 seconds console.log('[Game1] All messages printed. Starting 2s timer to expand message-container height...'); @@ -146,6 +200,8 @@ document.addEventListener('DOMContentLoaded', async () => { // Add event listener for Enter key inputField.addEventListener('keypress', async (e) => { if (e.key === 'Enter') { + stillPlayingSound = false; + sequenceFinished = false; const message = inputField.value.trim(); if (message && apiEchoUrl) { inputField.value = ''; @@ -163,6 +219,8 @@ document.addEventListener('DOMContentLoaded', async () => { }); console.log('[Game1] message-container height changed to 400vh and input enabled'); + sequenceFinished = true; + console.log('[Game1] sequenceFinished is now TRUE'); }, 2000); } }; diff --git a/assets/styles/game1.css b/assets/styles/game1.css index 2028486..83fa243 100644 --- a/assets/styles/game1.css +++ b/assets/styles/game1.css @@ -47,6 +47,10 @@ div#message-container { font-size: 20px; } +div.message { + color: #C0C0C0; +} + div#input { padding: 20px; } @@ -54,10 +58,32 @@ div#input { input#input-message { width: 100%; padding: 10px; - background: #000; - border: 1px solid #F00; - color: #F00; + background: #111; + border: 1px solid #A00000; + color: #C0C0C0; font-size: 18px; box-sizing: border-box; font-family: monospace; } + +.flash-red::after { + content: ''; + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 100px; + pointer-events: none; + background: linear-gradient(to top, rgba(255, 0, 0, 0.8), transparent); + animation: flash-red-anim 0.1s linear forwards; + z-index: 9999; +} + +@keyframes flash-red-anim { + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } +} diff --git a/public/audio/message.mp3 b/public/audio/message.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..20b73d1faf6e96267ceaf74a089f7b8045b657de GIT binary patch literal 14744 zcmdtJWmFtN*Dg9UxVuAecXxLg9D=(OEI0%h++BhOcL@;OA-FpscoJL_APL%seDD44 z&wJN8=dAPNJgd8FR(I{HJ++_OwY$1XS&kPD^bg#+1_lN%DdWqj<7VgOYx&j|1afVBU~Ft`_ky=C2nq^{dO=D`O5O{~%E}sE(EftHzP|An%+1ZMyM-`_vK004lOx-`|Kq4K;O++1Ano&T*KKNTJPcPI$F`%jPbYPDDY z{ow!F6nNNuF%UQkwDklJLKZDHGA%_1vmjDB-_62@>C3VJf?S%lzPvn)J_FnQQp7|^ zyLZ1r!;(0#K0kH>`*V-nf31OM z3Peo>LbmuzMY;d^Q9^6THw;Jg^T|}<--7+Qs}SH6=?@To`8RZD5D9{bglq>Itx~c` z!2Hvf21_rwy3&QfvF{4&TTo5F1`x?nh$CbmgxN|UEA0U;F*+Qf=?lU@h$YVEfE1}5 z2^SM+U_kpyA&ZwCAFir~lA0T5rQpPmbX!`Jhj2S+tUYs-gXmJv@dojPRvs=|LgkFPmLpz1cVur|Og@8Q04F8s~PP<>tsntj&d;<&{) z8Cxr2SV|N0RJhjoX_J(H3TS1v<&L(yb!vNCUOrTxb7x%AlC#_(O_IS%JI6iEIe^V_ zr}vtGcbUFx0>^MPk-yV(5SX!^Op*WKR`|lS7%cU6yb%A-EdV$lt&iFv z_+q)sE7-ISQbGV_Q2paC_zn*2gQx(Ts=d(sn0g+L1B$1NhHsePrK$1A0A+8S9p4$@ z$cNSxIZ$m8hPu$|NQ~SVYF{t;{S}bg&5MzZrMVlPR%T?vvQCl3`Td(L&Qv*(yLYFH zmamOLQ$T`^sax_KXeh5qqWBHW1wR(5>Wc-bntGFb0PfH=fMqj*fBHQ~zgz^hkKi;@ zz_Cek%dQ{|SmwB%Q8NoQj33tW7)|G2(4?U#5Tz^a*; zkOsAzt%Q0p)m^L5FOb9V!MVx;%Fj=T#$EYg8E7{69jqR**+ghj@b_<8CNy`HJeqBn z=+v-s*@jO%k!&lctY^RhlZ(fcNyO3{IFf2Jbp6?kfEwy&CmG2Bp4HVXTRbtF*zN3Qz?4VOFtjpNW@(yR^`4;$D?V*=_VHxP%1lw zB2uR*d)GIdxmHT`!~`S1w803AUbvqhh`|8l7CHi_b|-)vM;LX|p{_cVHm{6vS!Jtm zy5E0_06fM5023N;;tO2Y-(2?g%TNN(_AgEK{2??;@XnjB$`9fnFu?!wvs$N=zN-{1 zRgGihF+42Hzmb!^S>W6vj+#o}1;V9-FcdSqyDkUEiU{jU@zS5kKl;Oxa59Ugf@iia zA}oCm4!?lIRO0rkOO%}Lee;8?zM7YYOGCu&tG@8!6M@!hAS+7?sj=o%6gv+$7cVX5 z`@}2-(iu4?F-i=n<=ywNpNRHI+N8$7^12~?Q%%e-c5RaMPN^<;p!`PLWIYw@8UTd6 zAn3!OEzVc7k+FW@!P6F2QVmcrTujR?JE#42y=$nK5)=aiTi*O96{PfA1l>l*^U?;_ z6@uiWCve*uoK=md@%XD1&`bxm9w>i*VH=VHrEG$nA&F=k@#-LWkW}crs22o`PKT(! z&j;nQ5L)N?EC3e;JJqT$7MIUq_8mtYWdLPw@_?aveNlnqO>sC3@u=BN3__L%pcH}_p)ooJgC7tBYo{C`;AL9+n1r5h=~;TT`KMhi^}>g z3vqCm$x>DzA&Ppf&}(HIr(_)8zy=3D)9~@*&UZ*PmM~|7nj$N5{T!392;oOipb5x( z%yDe!@Qq(G(2?JbNI@_tMlUwH@n0X(U;N!8d7I1CYOOy;@V*0c85tPFJQ-^hRNEi@ z#@%U3*2EsYeyS1s`B;C4fkw5G6e|XnTvYmLSQ1y*iI#KJBaXyNn-R)3)C>PZJ$Ov8 z=ebwnHFXS5wgl8QU~EyZopma2?qgZb2734}8G|Gte%wi67b4M;yFcb9fk%J1Nt}TN z|KA6^rPlo`=-Lh(Ft88^M2(4xx0KCq_jY{92)f&sMtSbM0*OI3y!v;c1~DI_xWg z4sc8i1QMFV$9NU{t`tXVkZU4ZKb@*rMY~M$Vs^|1ZNvayDA$>PatW{bYxU5N1j#;Z1iwg9bOks1pt^<-qgq#8g*In-uip{(e7C8;JqZ`9G}w-Q2z{>tgf8zY zGSG^pao!S|toKAFv7jdDC@}-T4@}SHYa;ILr3OzLwTjge;=+5$N#2E3I9LkU-F=^W zPgu!BNdeo^3Otyy?h}q26&4kW9G1+KuUf!F_IIipFZ1=eXyeFG=%`=9tUa^awdDEa zpMI||K`wf0lE`^+utvm26z28V*9Y~6&&~@RWIRj=Ez$v1L^Dewjv?8=!+^~_2+8BE zwsUf!P=1n%hT};sA^(_i{qPeFO1YzWc7s>gEA|>2{B~1S`BdpGcAF`>T%&awfwR&I zCQYrT<-~Pqm(OdQxWyr8H|D@on=U!^D~y^lzxd4)R>t{dapG%y4WOv~`|E(A@|wXC zX(_suz%FuRxpB=Uj|GeGh}0Gy(7h>3Jf1}5R(R*Luup%8yC)Udl~8&xeqDFdtLLO8 zXmfmgQW2xK)!(`EU*EEHnGJBY2t&>l?AbL6-3t6(lNT-ehCdjz<7 zCiu}RWRNN#*I1`$z|Ne{wx=pDsqt2T1r`GWAtw35(V*~HNHf5t5`1HcMEc5gEDty8 zy@(!}F$)UbZ0jV8b3=CFL&rqmp-x=RSRE6+^EBNye|+fheK-55j|n1kO;HWQuxQFu z_98Sr@mlh<(STsv)f|<$>I?8N>b=&=#z$m72i7ynJ=yWGXwn#D4wM;0-VM&t?C%SE z&F5jJNYKrrhlL?g{sGL!luxD-a&Vzrv8DIO_4ZKQz``7+En0lSI9tlcZ^0#}PZn<2 z1Ly-vI2YnZkL{cs?p`yQ^YYo>?%tsm@}G8{^?SF!EqK)Paknba$XcHMqoI6h*7aWQ zt;%Se&vR(q3SB6?RqK+9-wGkWb3ix>=FIQTtbDbATqXY#=bX21+t9v~n0zQ7L#K?N z@#e#d>o7;Id4ErR<62yFqEA*=6_Jl8ido!pw6eIU>^|ibD!a$l2C7!rIq1xF7Z_e zY<2}~P!i0jmZS7~9py)Y3<$xlwX7NZsx^wP!_*ybygSxCGtO#h`hhhLZhc?HHbb&T{Se<3cKqwgGeG9O8?xkae{#@cf1#&5(& zP`1`Ebr!t%bM*6o$nVZR(^n+?Vrix=Wp;{B|1hgqORn z9+g{}TAe;iSq4Sbb_!OlaDa8f?b42yAfbO5IJOx-`k2a>Er5?-(GNU!oxW{w3WF-O zWv1T{&M3SAtP6qb9ZmiNAu%N(h<@SwB6jndPz$heR;MbCiUqNJo2uYDVg*>drkr3~ zI~MP-rtOIu14n)H(-d{7iMsZTk%;Rm(1p2!|Jphb?F;ZGqjvo#F}#jRvh0uNS0?%-MD+ zm-9iwvz*_-MA4kcdw@Oy?`&jCV9&@N9J59i@e!ncO!Isru zANJf5#J-&qe$_A4J1fui2~Pc^+x0=NfKKHXRt(H5>T0Uw5?!3Kv|!8}ntzNt10iI3 zEIg2<{Y_;R(Xh0gA~f_=Jn!wzuv!syGzU zx<;j;u(yS`x!MMSCf_gcTOi71JrO~ zi!L&OU2BrBLjLOR6x+iE2VAg4(f{bXOa`h%4PBnwmAER#+CDgYk8l$iZ~aiCtbe1V zCZwv8sw%O

fsJiC2F%=7Ep|(|nbAaU1e%<5CpQzx~O6X}P^C?W|m+E-0|3iqr-U zG;^4y0y4p%!zgC5Ld7PhRg$p^UL-!_L)fCJ*C28IRayR&$>c2}_vO;GMobeK(|wmD z$9&c@0xnvby&CU)ar`oY^7D1bD3UqKMgGW`W6e&GavGCp4taxW*kDs79C~%u4=Th~ zU|qi^ipLU^2m(P&tubKKh=dTrVXg=oG9FI|_%k?C3U>+>37%?+M`4-+MI{Og-!XYp zCLk-eyBKDIjG0xxz5HRnPMD)wGW_Pz+G<9X97yb+{MtHA-uOz9FIRXYi{tHqaPZH1 z*H|=jf-+{V%$X&>Ro~m+(klL+@`0ZGSdb9_MkI&dsz2Y zZ>W%`ol@ksc(7b35hOP<{&U#3oK$Gx=MF-k4R#$zZZ^vKX*p*>#m$YUjgR52%QT3$ zg@5P1Y^ccG$4i#N{~L<)iav{E{WHbM*-RbhHITd7Ijg0%Zf6;*vbki|e~s~>JlY^= zd9SK7k*-fP4YU1qX;-Cql2^m6>}@6~OK`iagBY3Hv%MX*GnqW6J2@l5dp$7!%ansB z!tZhT-%OuE)v464BJm^GxRgT2L6HfqYP^2PRFHwHJpIXY!~7^cejz>;$G2ZnNOgQ5 zMU|NbaZvq+#m%mj=Kb0>_GL@vB21)gOfE$Kfp_13#=0bGdgjg&rZt{=C2^-Qs>l%_ zq}_DsP@UjHxs;VTrM`DgRn>IHbIbKHYrRUPp)sTw9UFHPqxLYxv0})1Gb5+zOF~KV z%^xyuRyrolr;Rn=S-^wl_T*WdULL_>RAqakCDiISe46TN8zZ;F&q%^J}}Nq90R zG;+ULz+L|Rsb|T-Gfhf3iHRmP3@5czT-n7-+F5OEpu=+lHk1|ksBJLDf{e+(Sz*!O z`3i8~Qoe1%AWoO3qXR+$dZWzeW?MfJ1ib2BtrSfZ)uT8q2=D{li}BVzYNuKMW84A^ z5#3}i@MTnWJvK78RI0_tc=2&Bj9V;sd@5~g)f<)gs4}|EyIaCpKAp^s@hG82*v*GJ z_RPLJ;0U9pA5NeQ0e&k7*+1F$|E_)by~}Wv3U<39L?=Y{+6fIO4NtrA2jOUBZ@$xI z14WDvAd|6ioa|_gpI#&MpjY!#w}>A!iaR;PIg`qV>~Jt~aJVc1_4)}V%;0Y}sV+uf zob<};6~pxePZhoqi3yPfiM+g$-0-Z()d@X$j#y$gXRk?=_H;Na5D4X`3hFV5VPtIf zm*UV^s(BF383G*dFHqSz52p4>O*s79;!)iibk+`u6N^|yc%OduwazJlH!Jmu-MD!o zMQf74w;J-SD8*MUp{Kb~d1$Yz&#wy&nQzM_>c!TwSnvYXqm>vS$%r<}ZTI^M<8c^}y zv*WA0H$Rzro?-UKhv^JKaLLJ+9ihpi+Eg55rqp1nLF7dIzi^8*)b?~NUY)-`s_tWE zbgCSy?tgd^zV=FSkvd4eACsX%7c8DRdRVHpOWAqUG`F4in?NPC{C-fVZ)#MSX&sU$ zQriV*4T2zgB2Ng*&5nkarf^a5flx?QvMs)i51?bqxwPSt5~6nRClBpgdkQi+8fFi0 zJvo2n4VK6>D0o9dh2g?6X_^?buuV9C%qBy{Z9!2MS~HD;^f^O|Wg~snQzElX+Ya#2ylQ2Uf)Y5%>pzB%g-B`ek?nG+ z8NNzUtz<;CNH>$P(++AxpOYO|ZziN49YtEf_neZ@K}f#o3;F zzPt&te9bkG@@*XJ7|~lwJFR6TE@pH-W6jXn2NlkBln2(h&+l8VF8hhJ+DLQ2NEeY9 z)0DSF`FKWk;wH5W0Ja@L9a!jD2d!j}jem}0O%7VMHtFd2=F-g!J|OSmEqUU$eLpE+ zUoLHHyLpKF$PBFo`h@o4;gJ#4tYFMA5Q0d=?6I77L>Q916$_%2+-y46cl@$}I7J=u zTdU#kzZ`~n>MtwaB4I$$)Sc)Ci5gp#J8OBAb6Y(s*ESwKA1fUvz{SHeLP)>e*lpj> z`}+3(l;~rHt`=^iXm1Hx1aTVTPJ#hvu`-vSx}dIgZ=pw#dXz{ur-iA7ms9zp8IN4wZ!sT7Kfm_NdHzIV-50m5UoJh!Yp_n$o?q#7QM8jJl!Z5v&K9wQ8GuCpjXhgGWBPuwP zZn24qta}yXAn*0jw6fYjP}|iqv`)vF*!tHd9kv2j{M-XuwP9noNm)Kg~C^AqSYXk_ykO5zF7+r(XXNcE-j6G@v&rY>f<%+Z5X( zJEaV38Yg)P@=`by9uUH^X?50F>F$>-yX+axbu@tx`)-l74I>+jUi0JYG}Y*R0+VD6 z9pDy<3Z_^+yod&x0bwFhr*Im*26Nuwp~LX;f=fZ6tt#sS6?S%S)gII*V(zs}1SrjS>%Sw?>so zmN3$JS^ns{;GZ9u~m?vBf4~Stk?R}1Kj_nS&@~Xqt;^*J# zR5hfcNlPbqqg~=We>$U%-sK-8T#%ARe`5U+1csuh)R}xEZN{XQEkhFjeT7l$$k-Wd zez9%(lLt*-AoOFM&tA%y2SpSifkD`{9F)6hW9^D0@;Z7E(dI7zL6UE`?OL-_c+IB_#G( z2|lD8vtgHk&)AfuD{U7szJbBjE|mmNvL~byF^GyFDfJtK9Iu=(h3v+G1B4qR`Z`!7 zIF{3%%Gklk&?d#Z#^$rLh!R8RlA%)jv@)UolFJCJu@&6b6R< zw4Oc1Dd&zbh&Wa>&R+zX5_10c|T{3yR zW3v<0)s=i6Sby5Ru1)zb1Ds{LA1E`H;~-)>Q3)LKa0plvdwg2P+Af7=C6RbAHvR5X{An$vct22iHk(*A^JtZ%Yne^ zzfh6)O9yqVq%@uXcBN6PIN>Ig{fy@1I-T*ac4nCBdE=-;#O0;Bp|~Nsb>_M`<@HOf zKj1Q?kTrUKKCKQH^FhH;qBY2*9|-@ABVVJE6d)HFvGaR1*qyK5RO-Hr6k2*C`9ze*pIejL6kXjNoFAVqwRj0OJ#5T9fD1htYP$1~t>g4x7G=j6Ld~32X@=?I5UA8_QiXddI%`2rj~FAus>@p0uJV)E0(~6@zGF=1I%Rz%2VZ=$hrhD~pIgprQFI6A(a*t}VkTauu|aoQSGr7KLHA zD78B!Wax`3)G>eIhMDwXJ9&i^c7{sA?%59tVUxfV&>cshxVcf#92qhPBp_Q>zGcK}Rnd9B9 zPaSXbCJ|E}1jwG6{*pkIP%NM2O8=}by<5D3SkCRNkAEMC_Ys*<&NP3uO{s?(Zy zl6yYAUqxx9yS>x;MKmVk38-k9DfYSbFU9_3F?$ja@2(l}c2^6-KUJ{L5M^F`sPJMH zSQh=#iBt)Zdhdg{y*DXBF|>;!)L4R(0Mq4bmalh}Dnv>{Fseu4x}b^8ru}mH^ICIU z>?@{chrO|=*jJ`9O#vtI0-LLY7!EKph!_JK&-$y%$JxrdjM?+Vo?h!z4-2Z^V7JRO zt6E^qtS@Wy`uY@i)n)ZsNhIxe-pVk z64o`nP-UE@*|~bEvdiYR1rRfeJc_B?G{wTvtZ@9o&mr{za@t!V5A=jWiTKR!5BKSs zZ{n0UbnY40y}Jtwq#enk)8gpcp&D8#g+%6k(Gh-3k-6}}eFuNY`gX=b{`sDJ7<`tR z#)CpRv4a9BrEP7i)*iRy*~;(YdSb?>bb&-C(v5ZI7{mczFO>4w7$sWLfIlrBTD)-| zw2GXL;ZX^t#4M464BwsbcV_M8?CS(59g5p8r<2K!W=tT?X4bW!(xl-1h2Ml%LC!@! zb;~z}dg)K_&{f?EiO>6fl|m7R&q_^$`uYT*nM_G1nDWh5E3*yKG}jb`F|cz7d|ve* zD=lO=V{pKO(@;S)VEtnW%zJGs;+1f1m{$p%qgDo0(#?W9Lgg-Evkb)}YJP#QaLdW9c zOxCTpzBa|DxTEw_RbEH`Er^q|I6-hvft&SMkXmp?%=@StVlurxsz@uPb1CA7KAtM^Wbi$Ro$DzcpWXai^3Z`MCvVIyn@0x zE@tpR)FXlg<@Dsxov*E}J~tSzc6W&7QV*|R>!{a+Ga$iQnGR0#xvdq69*Uu0}B`jl3D)-MZg$EOceA*t?Ydt%2)zce@!4a5BxFB>Kg6jxu#?B zS*&;Myq=V>St;?iQ_gE<737lN_PV9h-G5(zmapI)&TBPgCO=R9Goek4xdUO4|LE(x zMkAjV8TJ#@S-nXPOv1GXZ5^vzy`XY)jeOAyU*|Wmi9?wbq9N7N5psEgz`b}~XsgDv z$E|0DQn8-G;fzLHavZwRGP(D&ZAb?D))9O*;y~VCRN5}z4qJ!?F}_>|&p>MF|Kk(X zRWt%sLxj9yly_{aTCx85gxn0vKyv%u3V{SVLuBM?2j&tQJn_~_*w&CVZO-w7ITp-1 zZL`lidO1dsDg9bpl?j#H4IH6>xvDFXn0g-{U!AT$PB${hoe|y6rKf6)eoQ3kTaUCT zjWA1-Npqf6DYad;m}WB~=zlNqJ=cRiHdnj{09;Tfr%oh+3km{XMl6+} zMpB8?JbQc1&!Et&moLRumm~t<&c`gzuR&{BSlS5af8iN0Ss;WZd+xi|^eFL~Ebwei zjVZDLQ+@?!9yeYi3WL{I)5?4WJP-THPR|0ZPOhVG6EFV-x!kUeBrps10AuIQ;1|BX z|68HzXL&>eevbo>Z$9lKhPTzm{@#Sbh80h+L0ocs6p!?>T~1h~%9`$6oMNvI!}%}o zcEf&~w`=GojfF-wj)(=>?rt2cULff_8(f5qo`R}Ux2ni=k+JZ_&$2)F9j^@>O#Ltp z$7m1VNkZqW#zKT$)Xn_U$&`W$mTK|mhPSl!V?U~^^exo`S1#)IU)7jQZXm`Wf!_Z{ zxp*6j_I^`^4@wUY(ImEYf$n;w;1uK1BZrAY;-aXD%HI0Xp24s_x{(F^Ta*ce!}GmF zrVQo8mbQ00&S;$2$J~-L?2mhn*^IrZH3{(!3$%k__uGtLzSOWaI%;w_bft?!ufJxW z(=V1OlGYWeuKm3af;RYPBhgVd~kxgetH_ zcZu1dYUjK@Ilye?GoSzKU_D;RBAPN^yB-O+W3{+2rx`3Zcu4D*muc5Xk3i*islOf%ahVA4I;63 z7QUy!p?)>Jbh@FV-Ok>*4Qe#!w)wWCRO5@%aHmpFj?>fId&Ts^(laBC7(&GGx2ber zN;d_3R=-c*r3;vS8vh&Fd=K^L?wHy7KAV=B@$v!ifhE4)?kGDsl4s-NQPZNIH0n`t z`_=p4s&nit>Iea;gji2qIV5WG97jI{6wD;$Wg1T)4|?$xsR41I@Hg_%f4w?@;I>{2 z#93+prHuu|>`Sq>IK|IOGAx6#4|=0YY|0&DzlvGuq!rQMdBd2vQZtHhjg6d0gm6U~ z3{u&f2kE*XcvV56Y#a7@A_(^=L^s~8@;?n2VAcpVsdO4ZU1@c?&bq^dbW8DH4|Sfp zqxfGe6*fL+-QuZ6NXxA}uBJxwT`5Y}TeQbr7~HF5;0C$6`oVxf8>Cx^srx}i&#nOC zJ2|V{>Ua>8xs;v_oww7BRd-x=Uz-}6q(&NjMYLA?qZhC>p!rks`8n%cGcykDP~sH( zRZOdkQ$}9?Wy^~0pSK)ndgrZYbql`D85lrOgP0nrN>~Z4h*srSN^D4V63l9FvgGR< zsHjQ9o;8!#R~g>ENU4TX;yf0c_G5~k*N1U(Af#4!1=aC#fNMILs?=0KMxdvR z+%dQf`p~VP>18+*kYhKgFk9*Uru0TiNND+ED_lU7{)B9T#2U_-gH0v>af2|aZUg0s()d4KU6gW zxyrf>kPYW75A8L;tpDY-O-b#ZQa>b)mE%UJce9*uFPM%6XcrB5lz1vn`G4g)H6r!<#Mjuh(5}wmrKRx(uAYWe=Wj z8ag@Q&OmFJVhVPs&w`?8ciJ7OaIffS1ypnJrzu8TeL?}bKMbKv+>h3rlhkU`HXb`q z;Mea!`z^aDW%v>&OMkkS8%$^VkrUBCh5Cq-1AI3)v(2z-Yq>*g;Xnay*dS^xDTpY* zU6tlUkRGOcUQr|T&C7bnn%wL7g#~gIjtnET;N(eM?Vo}6L2w`b<4aX# zV=ry@y)oBc=6kHH_oy%qd&6HP^loWTPN`DLcEWdSGRB~q-_~#Gbyp2F98fOL8GUVA zt01jDme|=b&2t&(hKa(gOQ}a3u?fxFJ=P%bZhtIDt(>T!Q9OBm%5S@Qu|3RMGz#

QMZenWhVUqarpsVN&>g3w-vUtO^H zRW$q!xX|cS!q#%8_VlmctE}71jq`8#adP8u4Qu=F(LElP(vA`P0h#9K%nTD8CCmlFq1W z*k4z(xa*5;_8{b-NNo?JPQ~G7)MF7M2+FsDl}4I9#HmOpo03&HdORY1H9UUCFcf5T zUEaazX%6IsF@pL^aq`J5P^$J=hi#2#9aOG_-CLWFgTcsdf>}or>t;y zX=7iCAlrE6}{!n^bu_{iZrCq z(gIAVm`f3=P5sDS1AI205|5M z)CYm~e4T2&AeDkR{rA(;jrj{-u+CuR4h}MWwq>IQ2w!)_lzee;1gPw6Blw9O!z86A zdoI^=K3i5XFzlqgVJYU>JEszF54`Jmgo0ASjs@%iDEW6H=H}Je5}N(6z`oAVAON_f zzXvv|pL>qNFnpJMeO-Nhbig+t7MmUiu1&plJJp<&$+HRTC>_Z-WqrN8U?YWnuba~! zx%dY&#tlgpsZu4tL-cOpGvjQEjWG2X@o&xU`!XG9V0AZg9y@G13gN)Z)JS>!GnbRv z$PXyhxC;VIzs$gS`s>@8Jne<`_&OyF&|OH&E8R442DQ17U7pdg8FM)2qLDABJ1WaD?xTYR{t~5Aq>t?W#$2{oLJTF?xUDX zDf$en@L8Yx9xeVsP*j{rNHIsr8HbA-+%BjO+~CgBe8JMGCMva@5&9#ouqucv@`C*v zW%a5z6_Ue%*ryn z;W!eyg{LWV+lwlYJk}|2!1$ur?9TSHFO8L@#v*7n$?##^?+k3a7ddYn()g^EP}}tl zZW;}3>y2ZK!_{&>@bUO1O1uTD_b(diY4huDfR`ewF)8-FCq2yS!_n*(4XSi4HT&w{ zdz5>4qU+Vzd7P5do%q`UyK16WUuql}pu?^cAL4Rq&}y-epCQNx;qSd_0(2ry-yCxa zSjO@879%j_lhDV~;4ed;3QeMhFkIH*Dj1YG4%VYr4Tyf=v+0uPOwc}C9B5_y=@P*w z>M&685n3yWQ7B8kt$!5~6!M}T5-Yb3H9zYWJ_xZb$v7E(-_9;eyAlo!H4CG0Kv83F z-)x9_>AQpBip)H(&(`%5c=gG+|3&T}RHt>n0uQj4DQ!G`m6?A%NLtRZP;9Ie?TMB5 zO!J!REQukAD8C1)9LssA=?#!DjJsX$Kbvc7;jRJ1= zS_&0_x)L446a;do_s{n^s*0~Ag@RtMWPkimjhSf_TeDVVR|s{uf=X6>)|JNOsx?pA zQ=MI=A)?B!q}De(f;Y&=h9>9>LxAUBsFME?1Aym`!0CI3Xr)+O?N62BQT}=Ih4b?nT4m@FqN+_UW#PENv$u|LEggF&=~|>?C3SrIdjFlmC=;%JNcL z!RvG2+3KmG|MKt4I>TbU8WOw9&~e3)wAKdR(yBvx%)Wl$2N&aiYXR83 z%)Ydd6s)iBNlHOFFK(}E^;eoRwg{u;M?TGhwBa+mwU$rYfD*M=QV7)8{}BuMmz?Gn zp;Q+E5AHTMAUrq~wLo66aZy?S5V6Tploe*~GYeO{X2m^@lARIR8k?3Fj0QG-m`gch!j=kTbzsCllf(ERC+k_0@?>wf?>z5rT z^QiM`wJ$lI1XQ#$13!7QeNsw@P-S19RFt~<3$jE4J`i&PmoFJ=LteVZ29(IQzdp5q zsr*wW!GtpEAIrE(3~dNV3Iqyy;(W;kEHOo_P`(t`5BvvD)i4}|znjAJ{wMLjT)_Wiz5gW!JZRed3{4%SP&|gguqi`P$U$6E2*-OAQ7}{F{2ps} zceLXFmGb{j%K{HlHdk0b_X6ZQi-@p=v4m3K5t5`p;OCd^OjIC{yPKovM6Z~?N{YPuQm%IO4wEiE2Br)y) literal 0 HcmV?d00001 diff --git a/src/Game/Service/GameResponseService.php b/src/Game/Service/GameResponseService.php index 057b31d..0dfccb6 100644 --- a/src/Game/Service/GameResponseService.php +++ b/src/Game/Service/GameResponseService.php @@ -8,6 +8,8 @@ use App\Game\Entity\Player; use App\Game\Repository\SessionSettingRepository; use App\Tech\Entity\User; use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Mercure\HubInterface; +use Symfony\Component\Mercure\Update; class GameResponseService { @@ -15,10 +17,11 @@ class GameResponseService private Security $security, private PlayerService $playerService, private SessionSettingRepository $sessionSettingRepository, + private HubInterface $hub, ) { } - public function getGameResponse(string $raw) + public function getGameResponse(string $raw) : array { $info = json_decode($raw, true); @@ -83,8 +86,10 @@ class GameResponseService if(!in_array('chat', $rechten)) return ['result' => ['Unknown command']]; - $this->handleChatMessage($message); - break; + if($this->handleChatMessage($message, $player)) + return ['result' => ['succesfully send']]; + else + return ['result' => ['Error sending']]; case '/help': return ['result' => $this->getHelpCommand($rechten)]; case '/decode': @@ -101,7 +106,6 @@ class GameResponseService default: return ['result' => ['Unknown command']]; } - return []; } private function checkConsoleCommando(string $message, Player $player, bool $sudo = false) : array @@ -124,6 +128,9 @@ class GameResponseService $this->playerService->saveCurrentPwdOfPlayer($player, $newLocation); return ['result' => ['Path: ' . $newLocation]]; + case 'pwd': + $pwd = $this->playerService->getCurrentPwdOfPlayer($player); + return ['result' => ['Path: ' . $pwd]]; case 'rm': break; case 'sudo': @@ -161,6 +168,13 @@ class GameResponseService $messages[] = ' USAGE: /decode {message}'; $messages[] = ''; break; + case 'pwd': + $messages[] = 'pwd'; + $messages[] = ' This message will let you know what your current location is.'; + $messages[] = ' It will show you the folder you are in so you can continue navigating the server.'; + $messages[] = ' USAGE: pwd'; + $messages[] = ''; + break; case 'cat': $messages[] = 'cat'; $messages[] = ' To read a file, use cat {filename}.'; @@ -210,9 +224,42 @@ class GameResponseService return $messages; } - private function handleChatMessage(string $message) + private function handleChatMessage(string $message, Player $player) : bool { + $messageParts = explode(' ', $message); + $toSingle = false; + if (isset($messageParts[1]) && + is_numeric($messageParts[1]) && + $messageParts[1] >= 1 && + $messageParts[1] <= 3) { + + $toSingle = true; + } + + $chatMessage = array_shift($messageParts); + $sendTo = 0; + + if ($toSingle) { + $sendTo = array_shift($messageParts); + } + + $message = $player->getUser()->getUsername() . ': ' . $chatMessage; + foreach($messageParts as $messagePart) { + $message .= $messagePart . ' '; + } + + $message = trim($message); + + $activeGame = $player->getSession()?->getId(); + + if(is_null($activeGame)) + return false; + + $topic = $_ENV['MERCURE_TOPIC_BASE'] . '/game/hub-' . $activeGame; + $this->hub->publish(new Update($topic, json_encode([$sendTo, $message]))); + + return true; } private function handleDecodeMessage(string $message, Player $player) diff --git a/templates/game/index.html.twig b/templates/game/index.html.twig index e34ee50..b3c67f4 100644 --- a/templates/game/index.html.twig +++ b/templates/game/index.html.twig @@ -15,10 +15,9 @@

@@ -28,6 +27,9 @@
+
From c384fc3dd58adaf095283a5ddec648d1c4b2ac93 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 7 Jan 2026 15:00:28 +0100 Subject: [PATCH 2/3] Sudo, rm and setup for ls --- src/Game/Enum/SessionSettingType.php | 1 + src/Game/Service/GameResponseService.php | 101 ++++++++++++++++++++++- src/Game/Service/PlayerService.php | 30 +++++++ 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/src/Game/Enum/SessionSettingType.php b/src/Game/Enum/SessionSettingType.php index f55a3fc..1cdd428 100644 --- a/src/Game/Enum/SessionSettingType.php +++ b/src/Game/Enum/SessionSettingType.php @@ -11,4 +11,5 @@ enum SessionSettingType: string case RIGHTS_FOR_PLAYER2 = 'RightsForPlayer2'; case RIGHTS_FOR_PLAYER3 = 'RightsForPlayer3'; case INVITE_CODE = 'InviteCode'; + case SET_OF_DELETED_FILES = 'SetOfDeletedFiles'; } diff --git a/src/Game/Service/GameResponseService.php b/src/Game/Service/GameResponseService.php index 0dfccb6..78e8454 100644 --- a/src/Game/Service/GameResponseService.php +++ b/src/Game/Service/GameResponseService.php @@ -116,8 +116,13 @@ class GameResponseService case 'help': return ['result' => $this->getHelpCommand($rechten)]; case 'ls': + if(!in_array('ls', $rechten)) + return ['result' => ['Unknown command']]; break; case 'cd': + if(!in_array('cd', $rechten)) + return ['result' => ['Unknown command']]; + $pwd = $this->playerService->getCurrentPwdOfPlayer($player); if(!$pwd) return ['result' => ['Unknown command']]; @@ -129,12 +134,39 @@ class GameResponseService $this->playerService->saveCurrentPwdOfPlayer($player, $newLocation); return ['result' => ['Path: ' . $newLocation]]; case 'pwd': + if(!in_array('pwd', $rechten)) + return ['result' => ['Unknown command']]; + $pwd = $this->playerService->getCurrentPwdOfPlayer($player); return ['result' => ['Path: ' . $pwd]]; case 'rm': - break; + if(!in_array('rm', $rechten)) + return ['result' => ['Unknown command']]; + + $pwd = $this->playerService->getCurrentPwdOfPlayer($player); + if(!$pwd) + return ['result' => ['Unknown command']]; + + if (!isset($messagePart[1])) { + return ['result' => ['Usage: rm {filename}']]; + } + + $filename = $messagePart[1]; + $fullPath = ($pwd === '/' ? '' : $pwd) . '/' . $filename; + + if(!$this->isAllowedToRemove($fullPath, $player, $sudo)) + return ['result' => ['You are not allowed to remove this file.']]; + + $this->playerService->addDeletedFileToSession($player, $fullPath); + return ['result' => ['File removed: ' . $filename]]; case 'sudo': - break; + if(!in_array('sudo', $rechten)) + return ['result' => ['Unknown command']]; + + $sudo = array_shift($messagePart); + $message = implode(' ', $messagePart); + + return $this->checkConsoleCommando($message, $player, true); default: return ['result' => ['Unknown command']]; } @@ -376,4 +408,69 @@ class GameResponseService return $paths; } + + private function isAllowedToRemove(string $file, Player $player, bool $sudo) : bool + { + if(!$this->fileExists($file)) + return false; + + if(str_starts_with($file, '/var/rapports/')) + return false; + + $rights = $this->getRechten($player); + + if(in_array('sudo', $rights) || $sudo) + return true; + + $sudoFiles = [ + '/var/arrest/handle.sh', + '/var/arrest/cell.sh', + '/var/marriage/divorce.sh', + ]; + + return !in_array($file, $sudoFiles); + } + + private function fileExists(string $file) + { + $files = $this->getAllPossibleFiles(); + + if(in_array($file, $files)) + return true; + + return false; + } + + private function getAllPossibleFiles() + { + $files = []; + + $files[] = '/var/arrest/handle.sh'; + $files[] = '/var/arrest/bars.sh'; + $files[] = '/var/arrest/cell.sh'; + $files[] = '/var/marriage/share.sh'; + $files[] = '/var/marriage/divorce.sh'; + $files[] = '/var/rapports/095_07-14.txt'; + $files[] = '/var/rapports/007_19-52.txt'; + $files[] = '/var/rapports/083_25-39.txt'; + $files[] = '/var/rapports/019_31-11.txt'; + $files[] = '/var/rapports/075_46-77.txt'; + $files[] = '/var/rapports/031_53-28.txt'; + $files[] = '/var/rapports/072_61-05.txt'; + $files[] = '/var/rapports/064_72-90.txt'; + $files[] = '/var/rapports/091_81-33.txt'; + $files[] = '/var/rapports/079_89-47.txt'; + $files[] = '/var/rapports/098_92-14.txt'; + $files[] = '/var/rapports/012_94-31.txt'; + $files[] = '/var/rapports/016_98-07.txt'; + $files[] = '/var/rapports/087_102-45.txt'; + $files[] = '/var/rapports/094_110-19.txt'; + $files[] = '/var/rapports/063_117-56.txt'; + $files[] = '/var/rapports/017_123-88.txt'; + $files[] = '/var/rapports/093_138-24.txt'; + $files[] = '/var/rapports/001_145-93.txt'; + $files[] = '/var/rapports/index.txt'; + + return $files; + } } diff --git a/src/Game/Service/PlayerService.php b/src/Game/Service/PlayerService.php index 23e8f5c..4f43c63 100644 --- a/src/Game/Service/PlayerService.php +++ b/src/Game/Service/PlayerService.php @@ -4,6 +4,7 @@ namespace App\Game\Service; use App\Game\Entity\Game; use App\Game\Entity\Player; +use App\Game\Entity\SessionSetting; use App\Game\Enum\SessionSettingType; use App\Game\Enum\SessionStatus; use App\Game\Repository\PlayerRepository; @@ -73,4 +74,33 @@ class PlayerService $this->entityManager->persist($setting); $this->entityManager->flush(); } + + public function getDeletedFilesOfSession(Player $player): array + { + $setting = $this->sessionSettingRepository->getSetting($player->getSession(), SessionSettingType::SET_OF_DELETED_FILES); + if (!$setting || !$setting->getValue()) { + return []; + } + + return json_decode($setting->getValue(), true) ?? []; + } + + public function addDeletedFileToSession(Player $player, string $filename): void + { + $setting = $this->sessionSettingRepository->getSetting($player->getSession(), SessionSettingType::SET_OF_DELETED_FILES); + if (!$setting) { + $setting = new SessionSetting(); + $setting->setSession($player->getSession()); + $setting->setName(SessionSettingType::SET_OF_DELETED_FILES); + } + + $deletedFiles = json_decode($setting->getValue() ?? '[]', true) ?? []; + if (!in_array($filename, $deletedFiles)) { + $deletedFiles[] = $filename; + $setting->setValue(json_encode($deletedFiles)); + + $this->entityManager->persist($setting); + $this->entityManager->flush(); + } + } } From 5f6ea89179b0b50da2496c7eb078d409a38cf665 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 7 Jan 2026 17:45:17 +0100 Subject: [PATCH 3/3] ls --- src/Game/Service/GameResponseService.php | 49 +++++++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/Game/Service/GameResponseService.php b/src/Game/Service/GameResponseService.php index 78e8454..8168f99 100644 --- a/src/Game/Service/GameResponseService.php +++ b/src/Game/Service/GameResponseService.php @@ -118,7 +118,9 @@ class GameResponseService case 'ls': if(!in_array('ls', $rechten)) return ['result' => ['Unknown command']]; - break; + + $files = $this->getAllCurrentFilesInDirectory($player); + return ['result' => $files]; case 'cd': if(!in_array('cd', $rechten)) return ['result' => ['Unknown command']]; @@ -170,7 +172,6 @@ class GameResponseService default: return ['result' => ['Unknown command']]; } - return []; } private function getHelpCommand(mixed $rechten) : array @@ -473,4 +474,48 @@ class GameResponseService return $files; } + + private function getAllCurrentFilesInDirectory(Player $player) : array + { + $pwd = $this->playerService->getCurrentPwdOfPlayer($player); + if (!$pwd) { + return []; + } + + $allPaths = $this->getAllPossiblePaths($player); + $allFiles = $this->getAllPossibleFiles(); + $deletedFiles = $this->playerService->getDeletedFilesOfSession($player); + + $entries = []; + + // Find directories in current pwd + foreach ($allPaths as $path) { + if ($path === $pwd) { + continue; + } + + // Check if $path is a direct child of $pwd + $parent = $this->getPrevPath($path); + if ($parent === $pwd) { + $parts = explode('/', $path); + $entries[] = [end($parts) . '/', 'dir']; + } + } + + // Find files in current pwd + foreach ($allFiles as $file) { + if (in_array($file, $deletedFiles)) { + continue; + } + + $parent = $this->getPrevPath($file); + if ($parent === $pwd) { + $parts = explode('/', $file); + $entries[] = [end($parts), 'file']; + } + } + + sort($entries); + return $entries; + } }