From 351bc56e73888e15c48cd88768be939b2857ebc8 Mon Sep 17 00:00:00 2001 From: Alla509 Date: Fri, 13 Mar 2026 12:57:21 +0500 Subject: [PATCH 01/14] =?UTF-8?q?1=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 5 + .idea/misc.xml | 6 + .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + ...0\274\320\265\321\201\321\202\321\200.iml" | 11 ++ .../gui/GameVisualizer$1.class" | Bin 0 -> 701 bytes .../gui/GameVisualizer$2.class" | Bin 0 -> 706 bytes .../gui/GameVisualizer$3.class" | Bin 0 -> 929 bytes .../gui/GameVisualizer.class" | Bin 0 -> 5627 bytes .../gui/GameWindow.class" | Bin 0 -> 856 bytes .../gui/LogWindow.class" | Bin 0 -> 2580 bytes .../gui/MainApplicationFrame$1.class" | Bin 0 -> 837 bytes .../gui/MainApplicationFrame.class" | Bin 0 -> 3268 bytes .../gui/MenuBarFactory.class" | Bin 0 -> 4267 bytes .../gui/RobotsProgram.class" | Bin 0 -> 5419 bytes .../log/LogChangeListener.class" | Bin 0 -> 148 bytes .../log/LogEntry.class" | Bin 0 -> 651 bytes .../log/LogLevel.class" | Bin 0 -> 1390 bytes .../log/LogWindowSource.class" | Bin 0 -> 2507 bytes .../log/Logger.class" | Bin 0 -> 858 bytes robots/src/gui/MainApplicationFrame.java | 134 ++++++------------ robots/src/gui/MenuBarFactory.java | 123 ++++++++++++++++ robots/src/gui/RobotsProgram.java | 97 +++++++++++-- 23 files changed, 284 insertions(+), 106 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 ".idea/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200.iml" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer$1.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer$2.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer$3.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameWindow.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/LogWindow.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame$1.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MenuBarFactory.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/RobotsProgram.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogChangeListener.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogEntry.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogLevel.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogWindowSource.class" create mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/Logger.class" create mode 100644 robots/src/gui/MenuBarFactory.java diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..f5bd2df --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..514065c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git "a/.idea/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200.iml" "b/.idea/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200.iml" new file mode 100644 index 0000000..8df936b --- /dev/null +++ "b/.idea/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200.iml" @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer$1.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer$1.class" new file mode 100644 index 0000000000000000000000000000000000000000..3f0bf76a0961dae8407aef10125e84e62b8f361e GIT binary patch literal 701 zcmZuv%Wl&^6g|_pacbPyd64pclmJPJ2;Hy=ViSdj6hZ}6P?wWrlunt9#f)7fR{RnY zFR|bQ_$b63CsKt{OTOpM@tHIC-tnKmzkUN4;BJ5o6nuCgHc@1#obXfL%~GkluO}yB znp%dk5g#&X#3<3DOeuzYtzj-xT+dgsUVEL|LJ3^|oUF_t#tN`4Zi zxzA8ckELxsqVL0XK`%fVk&ie;1yzRHS$T(YAocz(*nOIFTxpTJk-2j?Wz5duRUq{q-Bb0QUlHpy0y`v56u><%pm1Zk9^beKR={ z)6_DQjrf>JBSwiHWlAyJZw+&q;(ET4_1f#y7E0Li5rnvakfA=$WcMXsh_SR8SMrlE z&0U6KdMIu45q%%73wi;{hT>O#?uTm_6QS9Ox#l}*K>$pKHAj&NnZc@HUvd=MzcTlD1yq^-f&85y)_}@`G qGj-Pucnlv;a^rBRTN!&)wX5?s?htx(k`eCaOdUIdj0`l=XydVo7~%#J7FLjC$er;E-UtKfG>-ab zVh|{Xv@hO=(idIN?S_uSuwCgyFo(ON8LU}dOsyh?RRgAlE3g>WMxkuHvKDg4GnAsb{6o+X7s3r1hhC_}^C3SEgiloa(v`t8 zhIpmgGqHwi1_~C|ah+jf*`IAq!jKw?;MkKo%vWX?(KVtRNhqRZ;D&{p*d)ZFN@|-D z!3p;x9wh_mxeWHKOP5SqR1sSiZtJ@kzBuPm(e=u*t5Jxg@3~P z4rTHkZ@`@%_oY71k_p{PhQboI@?Mi6dnkfaZ+OgongVrOkf?JpTX%YZ5 siD@5sJe{j!|E*)Ajt4}Vz(c~uu!9Eq1noiwyHTcyN90Yyq{!3r2b$~E`2YX_ literal 0 HcmV?d00001 diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer.class" new file mode 100644 index 0000000000000000000000000000000000000000..5acf845665e412303baf88e4e2d4048153188b49 GIT binary patch literal 5627 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 T-o`t;KA}XV_NV0kB|85Gy&kNV literal 0 HcmV?d00001 diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame$1.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame$1.class" new file mode 100644 index 0000000000000000000000000000000000000000..68ca1c790248cf70d29189cf490ccf13f8dd5336 GIT binary patch literal 837 zcmZ`%+iuf95Iviuv1{DeDW$X}rCdTuf`lnN1|g~_6-a4{1WLu@##yCXx$9uB8y@*3 zByQq?58$H^vrYsRG?qLwJ32FS=JNB`_a6YdXgDaKXrbg{31x=rDL>~yrezcy4Npa= zQ-(?+-eoco{aE$0C}P-c^m16l)p!nTH~)7n!^W}&$Hf)63|r$&28Ud#{mCSfA=ffi z2MIqD^*x5NK9Ont5utjEU~LB#couvYRje}9at?f|192{t4vwW7#nb%}pJ z(Kf^waj4DKayf{>c^zj-C=R6IyLs7i9-1jrcU=|ED$kfP*nN@e7jdc?+?Ps;q!aNp z6)A4f+ADNi%=gXW$tuua#Rln3vN|Z{9LczH^On`AE$?1N9>9jZz6$@QGHsb;r6!TN%$f0LR!2Oh)1lM9LMKZ$MJ#vO_- c;V$6{sN){#65Wl5-Q2T=`=lMh*re(E4d8XcD*ylh literal 0 HcmV?d00001 diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame.class" new file mode 100644 index 0000000000000000000000000000000000000000..cf439b48ec4261b8a6ebbbc42510b3847d80a4b1 GIT binary patch literal 3268 zcmZ`*`*#!95&qVey;$Dh2ZmtGL!_k05{V&cN@JT)W5+~BG8EaSB(1|*TFZ;2T~)in zhNNvmOJAf-n!fUC={tSbP+L~;iTithTK_*iJ^k)oSq~0yjCS|lnYlCHd^2 z0A9rXAezvuBA}rKtqQuY>euw;0cXjyG6NIi1wCgdXdN~!(|tifbGUy-#SR5~8^p#8 zXVJCu7j(-ALPc{M+M%gh((*o$5Tk5MU^ zDj0^9G;c7{J@pD>X3nr2Iy=;cKI~UCs(>VIRz(!FESYI{L4gtz(uXxXAw*jj z3^TLf28qZK4Nu~;3fdgQJ!2QGw4*=`$M~;*M(l?*^dq8RC-+Bf%QY-lAgbW;a4iV2 zK_rdBkgs%1!&7)#K?j4-T_bL1E}2%^ULqb0MF3x2V&LsKuHgipp%Z#Ky)Ajjw_nYA zjAs`tJu7p>b5wkeUI|-gNe5pmh-c9(eokum0-k3i^xDU;g)s@GAp#{cZ&<}MdSP5o zxpv`t5Q8`+$VNIH2ulwiMhzL>Aik(!1ZS8hKfHu*ij=D4sp@lON5(j&k=Wx3+Ue3n zRi*cVD7Q6)t1ppzk0R-j6G5ECDFNO&4RIt0Z56z@=`c@5LBSJsNiwc6cLP39+Y$_D z2hJ>m+>kYnac$;2{I-gSP?bAE(B?U*lJ6v3q2G=N5l1EU$Ta#9VSuU-pNRm+vb#^{4 zD$=jsir`Ex007Yiv_I{opAl%EcNgEW~%MzD|tyEpt$5m8^WxT;}M&|7dRZu`AU zte+G=mIhmN5mj^%_7B04uW2ZNl`X0`!M2$z=KQj#ui|RkkTjn~QNuOSeFJ$twYUSz z3J!aBm_$K@P47PcZ-n@{;@y=?E8g1HO##LY4X@#K_MWscS7d&6SMuY7>LV}WQ1J~7 z-^3g2;H0jC$V2m3*DGtu4dN!grQ+KfZs9vDlLqEAbP6VfG0&KeIhQ5$f$+=X=@sIoowE6zmLV z5*bX5i$%vVb*pM;%2;NjeHTAb@ly>y!_OaZH@QS6PP=B-bWMXP+Fi`2*)yxLPH_%Q z#~$pr+bkD-;ync+Ie8?&qgmZ?CT(|IH1ijiQ--V~6(2N6#Vp6wE!UJ|uTJWtMyYJp z$mrRTRLXFiI;meNh&F&`ImLY5F1SXz4zQ{MVIQ^AM86}>Au(CZ%^8I$UFM@(&^J4y z7fkV8Sq!)fCXv|p=srwxnHIqo*{vItJ++ZkBQwVei_I@I;X*_4avTIR)%n5lm)o_8 z!u|Ff`+H?hDCqH>YM34r>JUAm?k2$*TQD3>eRhka%lb*^`AT#x^Zxy1zjU@bD;ii! zi)|^HNO{%iu4`tM)}>;N?WLCXIu8$DS5?jBL_y+=L<0LAbS;#hD@L zV>md>uF=HH7{{f!FY&E~@4Sui_YRjkSiY@XeJ!#MC9(na@;chq5bS(rxQxyc_9P;G zeaBX@udihx&1XnFOZZGAvWj2{MGDB;5qz8+K}PkxA+~ux?$q?IK*IkF^50kk34A@r|~iVgl5_~ zfj{FfJkP)${1tzb;QW0X99-eI0Iw&~J=uMsxwVX`;l2-`Hka{b;dQh0x|{mI^8|W? zUZ24(j7d-S__`HO+R=u8NKgJrX~O?s>|#NQ3znY>yiR-KwBs9?y zdiA8lLU_WfTg8)ev?o025J~1iU%wP(9^b4bYA0O5zteUPf+)*)1Wvt*X4Jx8L zR}A0cQ)qTZ%lK{?Z$--Z@y7@x?n(&TY3mo<70>r8&krllUsk~0C)qkn`Bxe270S+2 vdXA@M>b&TzdeMn{_!mzVpsQ$_RIw`ZR^n$9)=}m&!0og6gl|Fl#uwoK`eS`Q literal 0 HcmV?d00001 diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MenuBarFactory.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MenuBarFactory.class" new file mode 100644 index 0000000000000000000000000000000000000000..5077bfb0585de9d0f6f59ca0e6b2919d122f784b GIT binary patch literal 4267 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 literal 0 HcmV?d00001 diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogEntry.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogEntry.class" new file mode 100644 index 0000000000000000000000000000000000000000..1fb6da68d1578b4a45fea5aa2fc9882575fd4c0f GIT binary patch literal 651 zcmZuu%TB^j5IwgP3Z+0r@coP%sK$N(#*M*M)dhyc6)AXAOG=xxg~Y$oL}KE?5AdUm z(@P@=rpdfI=bV|jU*8{}0NOZIAt9qc){sSxK_A)^+w^STG4J|AK8P7|7q0KdmkgOk zvs*wO1qG^xA~Xig3mnr5oVFi_&ySP_^c4oj7!TriYE(Iz z!BEErWki5Yl3^m+q8^FjZS15nCi&!F)_8;RLV<*YJSU2}Ak=0JDIvoy_NbD~xCkyS MgX{l-_l3m44|QaCiU0rr literal 0 HcmV?d00001 diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogLevel.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogLevel.class" new file mode 100644 index 0000000000000000000000000000000000000000..0094a3418dec4101da550e71f1da65e73f6b643d GIT binary patch literal 1390 zcmZux-*XdH6#j1ZXO`PG4YXJ&SPN_hvQ^HKG&AR(V=e2(ivmYL%~VZK)XA6;zh$Z2op56RSAC&}V( zhWmSQu@mj3w5-0NWc@hn3e`^Di_+bwNF-2Lt44A|8Bvm(11e`^KdHNn z`vTXQuSIQ@Ql4Oh#XoT71fzfD(M^7=E1= zJx1XxY*4$w<1p+;fpS@3nPhh;ZMt;h((*IB+c@_->c3JSr<d>5_bI&PKK{j>EjOClYBq%BISAT z7qeAAw5(^i{1leauGqHGJfnT=I7SPM_KE8nZN_Lvnr z5YQ0R5kgp?{g$z2j1&#KFmiSAmbv5!gwI*F<&6mhlBwBdM9{1us-p$EKxfe@jO3ld zoMkUNccz_+yJQONDJ@vnDrUvZn|8rl5fC{Idw5$JuXt8*v^l}MbV0O>3pw1 zFqx}*=+v{b4Ldh(bje=Pg=*sW%JS*P^B&r@AG5MyYv{0m7(>C3*JdEo&h(kng zEHBeYvTCIv!ffige1BL+9})t4OAE%5XRVnMow6d(b0hBuI=;fx)Q^<+qdJaB2VvJN zIcuhd;{v*WeZuzK^(gw0lp&@BjwWlC^5&XZ%r>Bz_FT&@WK**m1_*Rlhccic9m5zA z&^$+FQ6QPT(O{>_TQ;@Jo`ysV(m1K(9Z9zC*yOGn(y}ytTE{5PkchJNHF+fAxhT#e zqamx~9L^KgHV5NQu}Fbfj!kl!&C;r;2o>l^rW&Y{X=I+>!vzf&b-a&p3S?VllZF&X zpk&zsp=2&6J+@*($7OuL7|WGKX+U6~JlkoOey6R1ZFm)zaQhpM>-suZ%eSH;JC+p~ z{>OVe7?ux$S$xYxuT_ZcNq-F&oMPHTz-L=qmToY)W_Kc<5lg6roq+zaBdrI~*E3R|L z51?Yd=|60&u9`NBCBg`lOvzZyS5>ww&9diCR%=t{VVh82#$?l!;%|6bWq^5nqrO6} ztSZ*oHpvQUj(L$lnYQM#XSm*F*D2BRl2fs%35{LHygTE_3aG2&wTHW~A25nVf&Dd& zzIL5{j_-P~i=Ak-+g-M#5caW-8hIX;`9OkgrHO4e$X~Wg&YEOjgTNIv&!I#1tor&x zRtI|!&ou6_CGz(pu1sYCNTjzQ((w&w=_hENk8h!E108C)@F{#cZs05~Pm7|<}OmOVdxQ0)Bm){X;kY|1AN9dlP{7nu6n>a9+78~dt ze10F1sln$%&mQughHiQQ{|~7#fNcbQJUxPT93_HdILuDCji9fNpbyiSq32I&r3X!~ zXolaUh=#OA`$EI)UqmDD*}vgC@?Y_#h;M-S28nNo_=f)%zImjo`4d^63R>ligY>N#U+)PF@(9NDY_yuygG&J zxQ-hPUhaL4FX%lA2VY`=USqh)zXVev3GPGaCB`-MYdArACltHdOSEt;#Gm9|$gfZ} zk;Ey52}ET2HgW1l&YP6L!fOAD!X@R)J3bl#1B<*RYv)_~mqH$he~&$J-^dTp9^>5y z@q;)0jmH=plhgR2O(x|ssHc-?W2JO5AqnbF>Nj0ASr2{yOR9qC2Fsi^@n)1| wtJ97QnGK*V;1~T@gj&Fg4{wAk2|XmeOUI3|%{9ap@|{&Xhs8&?)a~1U11cX3BLDyZ literal 0 HcmV?d00001 diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/Logger.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/Logger.class" new file mode 100644 index 0000000000000000000000000000000000000000..75ec665673e220b88222947353b42c43bb9971a1 GIT binary patch literal 858 zcmZ`%&2G~`7@SR0l4BqRfeqEs9=@nT#VyK5OE0ZM}p_I~62=9`(_A3wi-2XKI03l&sN7&dB{6KEgHQ`w7T9Q0ldkJTs> znA;2EFnc0Ub=*M%bu>&^Hkz;ntSAY3UJ?WG#=`^kp_ z?l;iFf{C_`MJx%}bWE(;Si=>8eN8usQw8GmUu&z&)s~T%w2O>9FJ>pkr zw0jQmiHiz0`PMfFY%thziz~a>gxlC+)xaHWmjsVd&HpveV4h*V`vuGY(zHpp_!mtR zJGh(EIRBx!I*X=5n$?*!F1n?fja&<0P|Zh<5V;#|77U<{d%uml)UBWSZ&-QL_Rq2L V8KzcVv=jlDG+rlipLhiiegTC7p$Py0 literal 0 HcmV?d00001 diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 62e943e..e236f67 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -2,14 +2,12 @@ import java.awt.Dimension; import java.awt.Toolkit; -import java.awt.event.KeyEvent; - +import java.awt.event.WindowEvent; +import java.awt.event.WindowAdapter; +import javax.swing.JOptionPane; import javax.swing.JDesktopPane; import javax.swing.JFrame; import javax.swing.JInternalFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; @@ -25,19 +23,16 @@ public class MainApplicationFrame extends JFrame { private final JDesktopPane desktopPane = new JDesktopPane(); - + public MainApplicationFrame() { - //Make the big window be indented 50 pixels from each edge - //of the screen. - int inset = 50; + int inset = 50; Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); setBounds(inset, inset, - screenSize.width - inset*2, - screenSize.height - inset*2); + screenSize.width - inset*2, + screenSize.height - inset*2); setContentPane(desktopPane); - - + LogWindow logWindow = createLogWindow(); addWindow(logWindow); @@ -45,10 +40,33 @@ public MainApplicationFrame() { gameWindow.setSize(400, 400); addWindow(gameWindow); - setJMenuBar(generateMenuBar()); - setDefaultCloseOperation(EXIT_ON_CLOSE); + MenuBarFactory menuFactory = new MenuBarFactory(this); + setJMenuBar(menuFactory.createMenuBar()); + + //закрытие приложения через крестик + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + exitApplication(); + } + }); + } + + public void exitApplication() { + //так как в UIManager уже установлен русский язык, то не нужно делать это дополнительно + int result = JOptionPane.showConfirmDialog( + this, + "Вы действительно хотите выйти из приложения?", + "Подтверждение выхода", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE + ); + + if (result == JOptionPane.YES_OPTION) { + System.exit(0); + } } - + protected LogWindow createLogWindow() { LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); @@ -59,96 +77,26 @@ protected LogWindow createLogWindow() Logger.debug("Протокол работает"); return logWindow; } - + protected void addWindow(JInternalFrame frame) { desktopPane.add(frame); frame.setVisible(true); } - -// protected JMenuBar createMenuBar() { -// JMenuBar menuBar = new JMenuBar(); -// -// //Set up the lone menu. -// JMenu menu = new JMenu("Document"); -// menu.setMnemonic(KeyEvent.VK_D); -// menuBar.add(menu); -// -// //Set up the first menu item. -// JMenuItem menuItem = new JMenuItem("New"); -// menuItem.setMnemonic(KeyEvent.VK_N); -// menuItem.setAccelerator(KeyStroke.getKeyStroke( -// KeyEvent.VK_N, ActionEvent.ALT_MASK)); -// menuItem.setActionCommand("new"); -//// menuItem.addActionListener(this); -// menu.add(menuItem); -// -// //Set up the second menu item. -// menuItem = new JMenuItem("Quit"); -// menuItem.setMnemonic(KeyEvent.VK_Q); -// menuItem.setAccelerator(KeyStroke.getKeyStroke( -// KeyEvent.VK_Q, ActionEvent.ALT_MASK)); -// menuItem.setActionCommand("quit"); -//// menuItem.addActionListener(this); -// menu.add(menuItem); -// -// return menuBar; -// } - - private JMenuBar generateMenuBar() - { - JMenuBar menuBar = new JMenuBar(); - - JMenu lookAndFeelMenu = new JMenu("Режим отображения"); - lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); - lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( - "Управление режимом отображения приложения"); - - { - JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); - systemLookAndFeel.addActionListener((event) -> { - setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - this.invalidate(); - }); - lookAndFeelMenu.add(systemLookAndFeel); - } - - { - JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_S); - crossplatformLookAndFeel.addActionListener((event) -> { - setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); - this.invalidate(); - }); - lookAndFeelMenu.add(crossplatformLookAndFeel); - } - JMenu testMenu = new JMenu("Тесты"); - testMenu.setMnemonic(KeyEvent.VK_T); - testMenu.getAccessibleContext().setAccessibleDescription( - "Тестовые команды"); - - { - JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); - addLogMessageItem.addActionListener((event) -> { - Logger.debug("Новая строка"); - }); - testMenu.add(addLogMessageItem); - } - - menuBar.add(lookAndFeelMenu); - menuBar.add(testMenu); - return menuBar; - } - - private void setLookAndFeel(String className) + /** + * Метод для смены Look&Feel (публичный, чтобы был доступен из MenuBarFactory) + */ + public void setLookAndFeel(String className) { try { UIManager.setLookAndFeel(className); + RobotsProgram.setRussianUIManagerText(); SwingUtilities.updateComponentTreeUI(this); } catch (ClassNotFoundException | InstantiationException - | IllegalAccessException | UnsupportedLookAndFeelException e) + | IllegalAccessException | UnsupportedLookAndFeelException e) { // just ignore } diff --git a/robots/src/gui/MenuBarFactory.java b/robots/src/gui/MenuBarFactory.java new file mode 100644 index 0000000..c286689 --- /dev/null +++ b/robots/src/gui/MenuBarFactory.java @@ -0,0 +1,123 @@ +package gui; + +import java.awt.event.KeyEvent; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.UIManager; +import log.Logger; + +/** + * Фабрика для создания строки меню приложения + */ +public class MenuBarFactory { + + private final MainApplicationFrame frame; + + public MenuBarFactory(MainApplicationFrame frame) { + this.frame = frame; + } + + /** + * Создает главную строку меню + */ + public JMenuBar createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + + menuBar.add(createFileMenu()); + menuBar.add(createLookAndFeelMenu()); + menuBar.add(createTestMenu()); + + return menuBar; + } + + /** + * Создает меню "Файл" + */ + private JMenu createFileMenu() { + JMenu fileMenu = new JMenu("Файл"); + fileMenu.setMnemonic(KeyEvent.VK_F); + fileMenu.getAccessibleContext().setAccessibleDescription( + "Управление файлами и приложением"); + + fileMenu.add(createExitMenuItem()); + + return fileMenu; + } + + /** + * Создает пункт меню для выхода из приложения + */ + private JMenuItem createExitMenuItem() { + JMenuItem exitMenuItem = new JMenuItem("Выход", KeyEvent.VK_X); + exitMenuItem.addActionListener((event) -> { + frame.exitApplication(); + }); + return exitMenuItem; + } + + + /** + * Создает меню "Режим отображения" + */ + private JMenu createLookAndFeelMenu() { + JMenu lookAndFeelMenu = new JMenu("Режим отображения"); + lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); + lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( + "Управление режимом отображения приложения"); + + lookAndFeelMenu.add(createSystemLookAndFeelItem()); + lookAndFeelMenu.add(createCrossPlatformLookAndFeelItem()); + + return lookAndFeelMenu; + } + + /** + * Создает пункт меню для системной схемы оформления + */ + private JMenuItem createSystemLookAndFeelItem() { + JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); + systemLookAndFeel.addActionListener((event) -> { + frame.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + frame.invalidate(); + }); + return systemLookAndFeel; + } + + /** + * Создает пункт меню для универсальной схемы оформления + */ + private JMenuItem createCrossPlatformLookAndFeelItem() { + JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_S); + crossplatformLookAndFeel.addActionListener((event) -> { + frame.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + frame.invalidate(); + }); + return crossplatformLookAndFeel; + } + + /** + * Создает меню "Тесты" + */ + private JMenu createTestMenu() { + JMenu testMenu = new JMenu("Тесты"); + testMenu.setMnemonic(KeyEvent.VK_T); + testMenu.getAccessibleContext().setAccessibleDescription( + "Тестовые команды"); + + testMenu.add(createAddLogMessageItem()); + + return testMenu; + } + + /** + * Создает пункт меню для добавления сообщения в лог + */ + private JMenuItem createAddLogMessageItem() { + JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); + addLogMessageItem.addActionListener((event) -> { + Logger.debug("Новая строка"); + }); + return addLogMessageItem; + } +} diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java index ae0930a..468cc39 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/robots/src/gui/RobotsProgram.java @@ -1,25 +1,96 @@ package gui; import java.awt.Frame; - +import java.util.Locale; import javax.swing.SwingUtilities; import javax.swing.UIManager; public class RobotsProgram { public static void main(String[] args) { - try { - UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); + Locale.setDefault(new Locale("ru", "RU")); + setRussianUIManagerText(); + try { + UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); // UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); // UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); - } catch (Exception e) { - e.printStackTrace(); - } - SwingUtilities.invokeLater(() -> { - MainApplicationFrame frame = new MainApplicationFrame(); - frame.pack(); - frame.setVisible(true); - frame.setExtendedState(Frame.MAXIMIZED_BOTH); - }); - }} + } catch (Exception e) { + e.printStackTrace(); + } + SwingUtilities.invokeLater(() -> { + MainApplicationFrame frame = new MainApplicationFrame(); + frame.pack(); + frame.setVisible(true); + frame.setExtendedState(Frame.MAXIMIZED_BOTH); + }); + } + + protected static void setRussianUIManagerText() { + // JOptionPane + UIManager.put("OptionPane.yesButtonText", "Да"); + UIManager.put("OptionPane.noButtonText", "Нет"); + UIManager.put("OptionPane.cancelButtonText", "Отмена"); + UIManager.put("OptionPane.okButtonText", "OK"); + UIManager.put("OptionPane.titleText", "Выберите опцию"); + + // JFileChooser + UIManager.put("FileChooser.openButtonText", "Открыть"); + UIManager.put("FileChooser.openButtonToolTipText", "Открыть выбранный файл"); + UIManager.put("FileChooser.saveButtonText", "Сохранить"); + UIManager.put("FileChooser.saveButtonToolTipText", "Сохранить выбранный файл"); + UIManager.put("FileChooser.cancelButtonText", "Отмена"); + UIManager.put("FileChooser.cancelButtonToolTipText", "Отменить выбор"); + UIManager.put("FileChooser.directoryOpenButtonText", "Открыть"); + UIManager.put("FileChooser.directoryOpenButtonToolTipText", "Открыть выбранную папку"); + + UIManager.put("FileChooser.lookInLabelText", "Папка:"); + UIManager.put("FileChooser.fileNameLabelText", "Имя файла:"); + UIManager.put("FileChooser.filesOfTypeLabelText", "Тип файлов:"); + UIManager.put("FileChooser.upFolderToolTipText", "На уровень вверх"); + UIManager.put("FileChooser.homeFolderToolTipText", "Домашняя папка"); + UIManager.put("FileChooser.newFolderToolTipText", "Создать новую папку"); + UIManager.put("FileChooser.listViewButtonToolTipText", "Список"); + UIManager.put("FileChooser.detailsViewButtonToolTipText", "Таблица"); + UIManager.put("FileChooser.fileNameHeaderText", "Имя"); + UIManager.put("FileChooser.fileSizeHeaderText", "Размер"); + UIManager.put("FileChooser.fileTypeHeaderText", "Тип"); + UIManager.put("FileChooser.fileDateHeaderText", "Изменен"); + UIManager.put("FileChooser.fileAttrHeaderText", "Атрибуты"); + + UIManager.put("FileChooser.acceptAllFileFilterText", "Все файлы"); + UIManager.put("FileChooser.newFolderDialogTitle", "Новая папка"); + UIManager.put("FileChooser.newFolderPromptText", "Имя новой папки:"); + + // JColorChooser + UIManager.put("ColorChooser.previewText", "Предпросмотр"); + UIManager.put("ColorChooser.okText", "OK"); + UIManager.put("ColorChooser.cancelText", "Отмена"); + UIManager.put("ColorChooser.resetText", "Сброс"); + UIManager.put("ColorChooser.sampleText", "Образец текста"); + + UIManager.put("ColorChooser.swatchesNameText", "Образцы"); + UIManager.put("ColorChooser.swatchesRecentText", "Последние:"); + UIManager.put("ColorChooser.hsvNameText", "HSV"); + UIManager.put("ColorChooser.hslNameText", "HSL"); + UIManager.put("ColorChooser.rgbNameText", "RGB"); + UIManager.put("ColorChooser.cmykNameText", "CMYK"); + + UIManager.put("ColorChooser.hueText", "Оттенок"); + UIManager.put("ColorChooser.saturationText", "Насыщенность"); + UIManager.put("ColorChooser.valueText", "Значение"); + UIManager.put("ColorChooser.lightnessText", "Освещенность"); + UIManager.put("ColorChooser.redText", "Красный"); + UIManager.put("ColorChooser.greenText", "Зеленый"); + UIManager.put("ColorChooser.blueText", "Синий"); + UIManager.put("ColorChooser.cyanText", "Голубой"); + UIManager.put("ColorChooser.magentaText", "Пурпурный"); + UIManager.put("ColorChooser.yellowText", "Желтый"); + UIManager.put("ColorChooser.blackText", "Черный"); + + // JOptionPane (дополнительные настройки) + UIManager.put("OptionPane.inputDialogTitle", "Ввод данных"); + UIManager.put("OptionPane.messageDialogTitle", "Сообщение"); + UIManager.put("OptionPane.titleText", "Выберите опцию"); + } +} From 860940f5ee9a426ea3cbe6b261a2b23fad955e10 Mon Sep 17 00:00:00 2001 From: Alla509 Date: Fri, 3 Apr 2026 15:02:43 +0500 Subject: [PATCH 02/14] =?UTF-8?q?2=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gui/LogWindow.class" | Bin 2580 -> 2580 bytes .../gui/MainApplicationFrame$1.class" | Bin 837 -> 837 bytes .../gui/MainApplicationFrame.class" | Bin 3268 -> 9333 bytes robots/src/gui/MainApplicationFrame.java | 136 ++++++++++++++++++ 4 files changed, 136 insertions(+) diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/LogWindow.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/LogWindow.class" index 5eb4e2625b7b6fe39e619d8bd9f45aed9c4614a1..6905baafb792d81cd7bf86219d879f8ecddb8388 100644 GIT binary patch delta 22 dcmbOtGDT#=Uv^F=23`g}1_lPE&1@WeOaMN=1O5O2 delta 22 dcmbOtGDT#=Uv^F*23`g}1_lP9&1@WeOaMRs1RekY diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame$1.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame$1.class" index 68ca1c790248cf70d29189cf490ccf13f8dd5336..0ce82699016b74424fdaa3e17d38dd9e2b024308 100644 GIT binary patch delta 23 fcmX@gc9dF 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 DMzsx~ diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index e236f67..1e3ef54 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -4,6 +4,11 @@ import java.awt.Toolkit; import java.awt.event.WindowEvent; import java.awt.event.WindowAdapter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; import javax.swing.JOptionPane; import javax.swing.JDesktopPane; import javax.swing.JFrame; @@ -23,8 +28,10 @@ public class MainApplicationFrame extends JFrame { private final JDesktopPane desktopPane = new JDesktopPane(); + private static final String CONFIG_FILE = System.getProperty("user.home") + File.separator + ".robot-config.properties"; public MainApplicationFrame() { + loadWindowState(); int inset = 50; Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); setBounds(inset, inset, @@ -40,6 +47,8 @@ public MainApplicationFrame() { gameWindow.setSize(400, 400); addWindow(gameWindow); + loadInternalWindowsState(); + MenuBarFactory menuFactory = new MenuBarFactory(this); setJMenuBar(menuFactory.createMenuBar()); @@ -54,6 +63,7 @@ public void windowClosing(WindowEvent e) { public void exitApplication() { //так как в UIManager уже установлен русский язык, то не нужно делать это дополнительно + saveWindowState(); int result = JOptionPane.showConfirmDialog( this, "Вы действительно хотите выйти из приложения?", @@ -101,4 +111,130 @@ public void setLookAndFeel(String className) // just ignore } } + + /** + * Загружает состояние главного окна из файла конфигурации. + * Если файл отсутствует, устанавливает размеры по умолчанию. + */ + private void loadWindowState() { + Properties props = new Properties(); + File configFile = new File(CONFIG_FILE); + if (!configFile.exists()) { + // геометрия по умолчанию (как было в конструкторе) + int inset = 50; + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + setBounds(inset, inset, + screenSize.width - inset * 2, + screenSize.height - inset * 2); + return; + } + + try (FileInputStream fis = new FileInputStream(configFile)) { + props.load(fis); + + int x = Integer.parseInt(props.getProperty("main.x", "50")); + int y = Integer.parseInt(props.getProperty("main.y", "50")); + int width = Integer.parseInt(props.getProperty("main.width", "800")); + int height = Integer.parseInt(props.getProperty("main.height", "600")); + int state = Integer.parseInt(props.getProperty("main.state", String.valueOf(JFrame.NORMAL))); + + setBounds(x, y, width, height); + setExtendedState(state); + } catch (IOException | NumberFormatException e) { + Logger.debug("Не удалось загрузить конфигурацию: " + e.getMessage()); + // в случае ошибки используем геометрию по умолчанию + int inset = 50; + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + setBounds(inset, inset, + screenSize.width - inset * 2, + screenSize.height - inset * 2); + } + } + + /* + * Сохраняет состояние главного окна и всех внутренних окон в файл. + */ + private void saveWindowState() { + Properties props = new Properties(); + + // Главное окно + props.setProperty("main.x", String.valueOf(getX())); + props.setProperty("main.y", String.valueOf(getY())); + props.setProperty("main.width", String.valueOf(getWidth())); + props.setProperty("main.height", String.valueOf(getHeight())); + props.setProperty("main.state", String.valueOf(getExtendedState())); + + // Внутренние окна + JInternalFrame[] frames = desktopPane.getAllFrames(); + for (int i = 0; i < frames.length; i++) { + JInternalFrame f = frames[i]; + String title = f.getTitle(); + if (title == null || title.isEmpty()) continue; + + String prefix = "window." + i + "."; + props.setProperty(prefix + "title", title); + props.setProperty(prefix + "x", String.valueOf(f.getX())); + props.setProperty(prefix + "y", String.valueOf(f.getY())); + props.setProperty(prefix + "width", String.valueOf(f.getWidth())); + props.setProperty(prefix + "height", String.valueOf(f.getHeight())); + props.setProperty(prefix + "icon", String.valueOf(f.isIcon())); + props.setProperty(prefix + "maximized", String.valueOf(f.isMaximum())); + } + + try (FileOutputStream fos = new FileOutputStream(CONFIG_FILE)) { + props.store(fos, "Robot program configuration"); + } catch (IOException e) { + Logger.debug("Не удалось сохранить конфигурацию: " + e.getMessage()); + } + } + + /** + * Восстанавливает состояние внутренних окон после их создания. + */ + private void loadInternalWindowsState() { + Properties props = new Properties(); + File configFile = new File(CONFIG_FILE); + if (!configFile.exists()) return; + + try (FileInputStream fis = new FileInputStream(configFile)) { + props.load(fis); + + JInternalFrame[] frames = desktopPane.getAllFrames(); + for (JInternalFrame frame : frames) { + String title = frame.getTitle(); + if (title == null) continue; + + // Ищем запись с таким же заголовком + for (String key : props.stringPropertyNames()) { + if (key.endsWith(".title") && props.getProperty(key).equals(title)) { + String prefix = key.substring(0, key.length() - 6); // убираем ".title" + try { + int x = Integer.parseInt(props.getProperty(prefix + ".x", "0")); + int y = Integer.parseInt(props.getProperty(prefix + ".y", "0")); + int w = Integer.parseInt(props.getProperty(prefix + ".width", "300")); + int h = Integer.parseInt(props.getProperty(prefix + ".height", "200")); + boolean icon = Boolean.parseBoolean(props.getProperty(prefix + ".icon", "false")); + boolean maximized = Boolean.parseBoolean(props.getProperty(prefix + ".maximized", "false")); + + frame.setBounds(x, y, w, h); + try { + if (maximized) { + frame.setMaximum(true); + } else if (icon) { + frame.setIcon(true); + } + } catch (java.beans.PropertyVetoException e) { + Logger.debug("Не удалось установить состояние окна " + title + ": " + e.getMessage()); + } + } catch (NumberFormatException e) { + Logger.debug("Ошибка парсинга конфигурации для окна " + title); + } + break; + } + } + } + } catch (IOException e) { + Logger.debug("Не удалось загрузить конфигурацию внутренних окон: " + e.getMessage()); + } + } } From 525f5f31abbc1a9bbeed21df934b9f9804b8b5a1 Mon Sep 17 00:00:00 2001 From: Alla509 Date: Fri, 10 Apr 2026 15:55:51 +0500 Subject: [PATCH 03/14] work with config in another class; determined names of windows in config --- .gitignore | 38 ++- .../gui/MainApplicationFrame$1.class" | Bin 837 -> 837 bytes .../gui/MainApplicationFrame.class" | Bin 9333 -> 6280 bytes robots/src/gui/ConfigManager.java | 96 +++++++ robots/src/gui/MainApplicationFrame.java | 242 ++++++------------ 5 files changed, 205 insertions(+), 171 deletions(-) create mode 100644 robots/src/gui/ConfigManager.java diff --git a/.gitignore b/.gitignore index 2c6eb38..3c86586 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,40 @@ /.metadata /robots/.settings /robots/bin -eclipse.bat \ No newline at end of file +eclipse.bat +out/ +/robots/.classpath +/robots/.project +# Translations +*.mo + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# IDEA +.idea/ +*.iml + +# Maven +target/ \ No newline at end of file diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame$1.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame$1.class" index 0ce82699016b74424fdaa3e17d38dd9e2b024308..9f61521e423079c3cfa637d457912c3cd635bc93 100644 GIT binary patch delta 23 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 WeewuF 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 Y`uTx;#{E?xzKU=Dk Date: Fri, 10 Apr 2026 16:13:46 +0500 Subject: [PATCH 04/14] Delete .idea directory --- .idea/.gitignore | 5 ----- .idea/misc.xml | 6 ------ .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ ...0\265\320\274\320\265\321\201\321\202\321\200.iml" | 11 ----------- 5 files changed, 36 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 ".idea/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200.iml" diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index f5bd2df..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 514065c..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git "a/.idea/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200.iml" "b/.idea/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200.iml" deleted file mode 100644 index 8df936b..0000000 --- "a/.idea/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200.iml" +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file From eb501bd7104f606c16ae29c9eb01fd4823af2fc5 Mon Sep 17 00:00:00 2001 From: Alla509 Date: Fri, 10 Apr 2026 16:14:52 +0500 Subject: [PATCH 05/14] =?UTF-8?q?Delete=20out/production/=D0=BE=D0=BE?= =?UTF-8?q?=D0=BF=204=20=D1=81=D0=B5=D0=BC=D0=B5=D1=81=D1=82=D1=80=20direc?= =?UTF-8?q?tory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gui/GameVisualizer$1.class" | Bin 701 -> 0 bytes .../gui/GameVisualizer$2.class" | Bin 706 -> 0 bytes .../gui/GameVisualizer$3.class" | Bin 929 -> 0 bytes .../gui/GameVisualizer.class" | Bin 5627 -> 0 bytes .../gui/GameWindow.class" | Bin 856 -> 0 bytes .../gui/LogWindow.class" | Bin 2580 -> 0 bytes .../gui/MainApplicationFrame$1.class" | Bin 837 -> 0 bytes .../gui/MainApplicationFrame.class" | Bin 6280 -> 0 bytes .../gui/MenuBarFactory.class" | Bin 4267 -> 0 bytes .../gui/RobotsProgram.class" | Bin 5419 -> 0 bytes .../log/LogChangeListener.class" | Bin 148 -> 0 bytes .../log/LogEntry.class" | Bin 651 -> 0 bytes .../log/LogLevel.class" | Bin 1390 -> 0 bytes .../log/LogWindowSource.class" | Bin 2507 -> 0 bytes .../log/Logger.class" | Bin 858 -> 0 bytes 15 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer$1.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer$2.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer$3.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameWindow.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/LogWindow.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame$1.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MenuBarFactory.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/RobotsProgram.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogChangeListener.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogEntry.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogLevel.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogWindowSource.class" delete mode 100644 "out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/Logger.class" diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer$1.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer$1.class" deleted file mode 100644 index 3f0bf76a0961dae8407aef10125e84e62b8f361e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 701 zcmZuv%Wl&^6g|_pacbPyd64pclmJPJ2;Hy=ViSdj6hZ}6P?wWrlunt9#f)7fR{RnY zFR|bQ_$b63CsKt{OTOpM@tHIC-tnKmzkUN4;BJ5o6nuCgHc@1#obXfL%~GkluO}yB znp%dk5g#&X#3<3DOeuzYtzj-xT+dgsUVEL|LJ3^|oUF_t#tN`4Zi zxzA8ckELxsqVL0XK`%fVk&ie;1yzRHS$T(YAocz(*nOIFTxpTJk-2j?Wz5duRUq{q-Bb0QUlHpy0y`v56u><%pm1Zk9^beKR={ z)6_DQjrf>JBSwiHWlAyJZw+&q;(ET4_1f#y7E0Li5rnvakfA=$WcMXsh_SR8SMrlE z&0U6KdMIu45q%%73wi;{hT>O#?uTm_6QS9Ox#l}*K>$pKHAj&NnZc@HUvd=MzcTlD1yq^-f&85y)_}@`G qGj-Pucnlv;a^rBRTN!&)wX5?s?htx(k`eCaOdUIdj0`l=XydVo7~%#J7FLjC$er;E-UtKfG>-ab zVh|{Xv@hO=(idIN?S_uSuwCgyFo(ON8LU}dOsyh?RRgAlE3g>WMxkuHvKDg4GnAsb{6o+X7s3r1hhC_}^C3SEgiloa(v`t8 zhIpmgGqHwi1_~C|ah+jf*`IAq!jKw?;MkKo%vWX?(KVtRNhqRZ;D&{p*d)ZFN@|-D z!3p;x9wh_mxeWHKOP5SqR1sSiZtJ@kzBuPm(e=u*t5Jxg@3~P z4rTHkZ@`@%_oY71k_p{PhQboI@?Mi6dnkfaZ+OgongVrOkf?JpTX%YZ5 siD@5sJe{j!|E*)Ajt4}Vz(c~uu!9Eq1noiwyHTcyN90Yyq{!3r2b$~E`2YX_ diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/GameVisualizer.class" deleted file mode 100644 index 5acf845665e412303baf88e4e2d4048153188b49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5627 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 R4zEurQK|hY`G1LyzX5u_tabnZ diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame$1.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/gui/MainApplicationFrame$1.class" deleted file mode 100644 index 9f61521e423079c3cfa637d457912c3cd635bc93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 837 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 Weewu)P*op)y$)F|>jLmwPISf~4p)(hRu4-f3 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 diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogEntry.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogEntry.class" deleted file mode 100644 index 1fb6da68d1578b4a45fea5aa2fc9882575fd4c0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 651 zcmZuu%TB^j5IwgP3Z+0r@coP%sK$N(#*M*M)dhyc6)AXAOG=xxg~Y$oL}KE?5AdUm z(@P@=rpdfI=bV|jU*8{}0NOZIAt9qc){sSxK_A)^+w^STG4J|AK8P7|7q0KdmkgOk zvs*wO1qG^xA~Xig3mnr5oVFi_&ySP_^c4oj7!TriYE(Iz z!BEErWki5Yl3^m+q8^FjZS15nCi&!F)_8;RLV<*YJSU2}Ak=0JDIvoy_NbD~xCkyS MgX{l-_l3m44|QaCiU0rr diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogLevel.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/LogLevel.class" deleted file mode 100644 index 0094a3418dec4101da550e71f1da65e73f6b643d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1390 zcmZux-*XdH6#j1ZXO`PG4YXJ&SPN_hvQ^HKG&AR(V=e2(ivmYL%~VZK)XA6;zh$Z2op56RSAC&}V( zhWmSQu@mj3w5-0NWc@hn3e`^Di_+bwNF-2Lt44A|8Bvm(11e`^KdHNn z`vTXQuSIQ@Ql4Oh#XoT71fzfD(M^7=E1= zJx1XxY*4$w<1p+;fpS@3nPhh;ZMt;h((*IB+c@_->c3JSr<d>5_bI&PKK{j>EjOClYBq%BISAT z7qeAAw5(^i{1leauGqHGJfnT=I7SPM_KE8nZN_Lvnr z5YQ0R5kgp?{g$z2j1&#KFmiSAmbv5!gwI*F<&6mhlBwBdM9{1us-p$EKxfe@jO3ld zoMkUNccz_+yJQONDJ@vnDrUvZn|8rl5fC{Idw5$JuXt8*v^l}MbV0O>3pw1 zFqx}*=+v{b4Ldh(bje=Pg=*sW%JS*P^B&r@AG5MyYv{0m7(>C3*JdEo&h(kng zEHBeYvTCIv!ffige1BL+9})t4OAE%5XRVnMow6d(b0hBuI=;fx)Q^<+qdJaB2VvJN zIcuhd;{v*WeZuzK^(gw0lp&@BjwWlC^5&XZ%r>Bz_FT&@WK**m1_*Rlhccic9m5zA z&^$+FQ6QPT(O{>_TQ;@Jo`ysV(m1K(9Z9zC*yOGn(y}ytTE{5PkchJNHF+fAxhT#e zqamx~9L^KgHV5NQu}Fbfj!kl!&C;r;2o>l^rW&Y{X=I+>!vzf&b-a&p3S?VllZF&X zpk&zsp=2&6J+@*($7OuL7|WGKX+U6~JlkoOey6R1ZFm)zaQhpM>-suZ%eSH;JC+p~ z{>OVe7?ux$S$xYxuT_ZcNq-F&oMPHTz-L=qmToY)W_Kc<5lg6roq+zaBdrI~*E3R|L z51?Yd=|60&u9`NBCBg`lOvzZyS5>ww&9diCR%=t{VVh82#$?l!;%|6bWq^5nqrO6} ztSZ*oHpvQUj(L$lnYQM#XSm*F*D2BRl2fs%35{LHygTE_3aG2&wTHW~A25nVf&Dd& zzIL5{j_-P~i=Ak-+g-M#5caW-8hIX;`9OkgrHO4e$X~Wg&YEOjgTNIv&!I#1tor&x zRtI|!&ou6_CGz(pu1sYCNTjzQ((w&w=_hENk8h!E108C)@F{#cZs05~Pm7|<}OmOVdxQ0)Bm){X;kY|1AN9dlP{7nu6n>a9+78~dt ze10F1sln$%&mQughHiQQ{|~7#fNcbQJUxPT93_HdILuDCji9fNpbyiSq32I&r3X!~ zXolaUh=#OA`$EI)UqmDD*}vgC@?Y_#h;M-S28nNo_=f)%zImjo`4d^63R>ligY>N#U+)PF@(9NDY_yuygG&J zxQ-hPUhaL4FX%lA2VY`=USqh)zXVev3GPGaCB`-MYdArACltHdOSEt;#Gm9|$gfZ} zk;Ey52}ET2HgW1l&YP6L!fOAD!X@R)J3bl#1B<*RYv)_~mqH$he~&$J-^dTp9^>5y z@q;)0jmH=plhgR2O(x|ssHc-?W2JO5AqnbF>Nj0ASr2{yOR9qC2Fsi^@n)1| wtJ97QnGK*V;1~T@gj&Fg4{wAk2|XmeOUI3|%{9ap@|{&Xhs8&?)a~1U11cX3BLDyZ diff --git "a/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/Logger.class" "b/out/production/\320\276\320\276\320\277 4 \321\201\320\265\320\274\320\265\321\201\321\202\321\200/log/Logger.class" deleted file mode 100644 index 75ec665673e220b88222947353b42c43bb9971a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 858 zcmZ`%&2G~`7@SR0l4BqRfeqEs9=@nT#VyK5OE0ZM}p_I~62=9`(_A3wi-2XKI03l&sN7&dB{6KEgHQ`w7T9Q0ldkJTs> znA;2EFnc0Ub=*M%bu>&^Hkz;ntSAY3UJ?WG#=`^kp_ z?l;iFf{C_`MJx%}bWE(;Si=>8eN8usQw8GmUu&z&)s~T%w2O>9FJ>pkr zw0jQmiHiz0`PMfFY%thziz~a>gxlC+)xaHWmjsVd&HpveV4h*V`vuGY(zHpp_!mtR zJGh(EIRBx!I*X=5n$?*!F1n?fja&<0P|Zh<5V;#|77U<{d%uml)UBWSZ&-QL_Rq2L V8KzcVv=jlDG+rlipLhiiegTC7p$Py0 From 942fce004dfb154e1b1c6386868cbcd8e3dbbbb9 Mon Sep 17 00:00:00 2001 From: Alla509 Date: Fri, 17 Apr 2026 16:22:13 +0500 Subject: [PATCH 06/14] another package for config, new classes to coordinate robot: RobotController, RobotModel, RobotCoordinatesWindow --- robots/src/{gui => config}/ConfigManager.java | 2 +- robots/src/gui/GameVisualizer.java | 215 ++++-------------- robots/src/gui/GameWindow.java | 12 +- robots/src/gui/MainApplicationFrame.java | 23 +- robots/src/gui/RobotController.java | 47 ++++ robots/src/gui/RobotCoordinatesWindow.java | 43 ++++ robots/src/gui/RobotModel.java | 90 ++++++++ 7 files changed, 244 insertions(+), 188 deletions(-) rename robots/src/{gui => config}/ConfigManager.java (99%) create mode 100644 robots/src/gui/RobotController.java create mode 100644 robots/src/gui/RobotCoordinatesWindow.java create mode 100644 robots/src/gui/RobotModel.java diff --git a/robots/src/gui/ConfigManager.java b/robots/src/config/ConfigManager.java similarity index 99% rename from robots/src/gui/ConfigManager.java rename to robots/src/config/ConfigManager.java index 875240d..06c5d42 100644 --- a/robots/src/gui/ConfigManager.java +++ b/robots/src/config/ConfigManager.java @@ -1,4 +1,4 @@ -package gui; +package config; import java.io.File; import java.io.FileInputStream; diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java index f82cfd8..d8803bb 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/robots/src/gui/GameVisualizer.java @@ -1,210 +1,71 @@ package gui; import java.awt.Color; -import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; -import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; -import java.util.Timer; -import java.util.TimerTask; - +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.JPanel; -public class GameVisualizer extends JPanel -{ - private final Timer m_timer = initTimer(); - - private static Timer initTimer() - { - Timer timer = new Timer("events generator", true); - return timer; - } - - private volatile double m_robotPositionX = 100; - private volatile double m_robotPositionY = 100; - private volatile double m_robotDirection = 0; +public class GameVisualizer extends JPanel implements PropertyChangeListener { + private final RobotModel model; - private volatile int m_targetPositionX = 150; - private volatile int m_targetPositionY = 100; - - private static final double maxVelocity = 0.1; - private static final double maxAngularVelocity = 0.001; - - public GameVisualizer() - { - m_timer.schedule(new TimerTask() - { - @Override - public void run() - { - onRedrawEvent(); - } - }, 0, 50); - m_timer.schedule(new TimerTask() - { - @Override - public void run() - { - onModelUpdateEvent(); - } - }, 0, 10); - addMouseListener(new MouseAdapter() - { + public GameVisualizer(RobotModel model) { + this.model = model; + model.addPropertyChangeListener(this); + + addMouseListener(new MouseAdapter() { @Override - public void mouseClicked(MouseEvent e) - { - setTargetPosition(e.getPoint()); - repaint(); + public void mouseClicked(MouseEvent e) { + model.setTarget(e.getPoint().x, e.getPoint().y); } }); setDoubleBuffered(true); } - protected void setTargetPosition(Point p) - { - m_targetPositionX = p.x; - m_targetPositionY = p.y; - } - - protected void onRedrawEvent() - { - EventQueue.invokeLater(this::repaint); - } - - private static double distance(double x1, double y1, double x2, double y2) - { - double diffX = x1 - x2; - double diffY = y1 - y2; - return Math.sqrt(diffX * diffX + diffY * diffY); - } - - private static double angleTo(double fromX, double fromY, double toX, double toY) - { - double diffX = toX - fromX; - double diffY = toY - fromY; - - return asNormalizedRadians(Math.atan2(diffY, diffX)); - } - - protected void onModelUpdateEvent() - { - double distance = distance(m_targetPositionX, m_targetPositionY, - m_robotPositionX, m_robotPositionY); - if (distance < 0.5) - { - return; - } - double velocity = maxVelocity; - double angleToTarget = angleTo(m_robotPositionX, m_robotPositionY, m_targetPositionX, m_targetPositionY); - double angularVelocity = 0; - if (angleToTarget > m_robotDirection) - { - angularVelocity = maxAngularVelocity; - } - if (angleToTarget < m_robotDirection) - { - angularVelocity = -maxAngularVelocity; - } - - moveRobot(velocity, angularVelocity, 10); - } - - private static double applyLimits(double value, double min, double max) - { - if (value < min) - return min; - if (value > max) - return max; - return value; - } - - private void moveRobot(double velocity, double angularVelocity, double duration) - { - velocity = applyLimits(velocity, 0, maxVelocity); - angularVelocity = applyLimits(angularVelocity, -maxAngularVelocity, maxAngularVelocity); - double newX = m_robotPositionX + velocity / angularVelocity * - (Math.sin(m_robotDirection + angularVelocity * duration) - - Math.sin(m_robotDirection)); - if (!Double.isFinite(newX)) - { - newX = m_robotPositionX + velocity * duration * Math.cos(m_robotDirection); - } - double newY = m_robotPositionY - velocity / angularVelocity * - (Math.cos(m_robotDirection + angularVelocity * duration) - - Math.cos(m_robotDirection)); - if (!Double.isFinite(newY)) - { - newY = m_robotPositionY + velocity * duration * Math.sin(m_robotDirection); - } - m_robotPositionX = newX; - m_robotPositionY = newY; - double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); - m_robotDirection = newDirection; + @Override + public void propertyChange(PropertyChangeEvent evt) { + repaint(); } - private static double asNormalizedRadians(double angle) - { - while (angle < 0) - { - angle += 2*Math.PI; - } - while (angle >= 2*Math.PI) - { - angle -= 2*Math.PI; - } - return angle; - } - - private static int round(double value) - { - return (int)(value + 0.5); - } - @Override - public void paint(Graphics g) - { - super.paint(g); - Graphics2D g2d = (Graphics2D)g; - drawRobot(g2d, round(m_robotPositionX), round(m_robotPositionY), m_robotDirection); - drawTarget(g2d, m_targetPositionX, m_targetPositionY); + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g; + drawRobot(g2d, (int)model.getRobotX(), (int)model.getRobotY(), model.getRobotDirection()); + drawTarget(g2d, model.getTargetX(), model.getTargetY()); } - - private static void fillOval(Graphics g, int centerX, int centerY, int diam1, int diam2) - { - g.fillOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); - } - - private static void drawOval(Graphics g, int centerX, int centerY, int diam1, int diam2) - { - g.drawOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); - } - - private void drawRobot(Graphics2D g, int x, int y, double direction) - { - int robotCenterX = round(m_robotPositionX); - int robotCenterY = round(m_robotPositionY); - AffineTransform t = AffineTransform.getRotateInstance(direction, robotCenterX, robotCenterY); + + private void drawRobot(Graphics2D g, int x, int y, double direction) { + AffineTransform t = AffineTransform.getRotateInstance(direction, x, y); g.setTransform(t); g.setColor(Color.MAGENTA); - fillOval(g, robotCenterX, robotCenterY, 30, 10); + fillOval(g, x, y, 30, 10); g.setColor(Color.BLACK); - drawOval(g, robotCenterX, robotCenterY, 30, 10); + drawOval(g, x, y, 30, 10); g.setColor(Color.WHITE); - fillOval(g, robotCenterX + 10, robotCenterY, 5, 5); + fillOval(g, x + 10, y, 5, 5); g.setColor(Color.BLACK); - drawOval(g, robotCenterX + 10, robotCenterY, 5, 5); + drawOval(g, x + 10, y, 5, 5); + g.setTransform(new AffineTransform()); } - - private void drawTarget(Graphics2D g, int x, int y) - { - AffineTransform t = AffineTransform.getRotateInstance(0, 0, 0); - g.setTransform(t); + + private void drawTarget(Graphics2D g, int x, int y) { + g.setTransform(new AffineTransform()); g.setColor(Color.GREEN); fillOval(g, x, y, 5, 5); g.setColor(Color.BLACK); drawOval(g, x, y, 5, 5); } + + private void fillOval(Graphics g, int centerX, int centerY, int diam1, int diam2) { + g.fillOval(centerX - diam1/2, centerY - diam2/2, diam1, diam2); + } + + private void drawOval(Graphics g, int centerX, int centerY, int diam1, int diam2) { + g.drawOval(centerX - diam1/2, centerY - diam2/2, diam1, diam2); + } } diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java index ecb63c0..51081cd 100644 --- a/robots/src/gui/GameWindow.java +++ b/robots/src/gui/GameWindow.java @@ -1,19 +1,15 @@ package gui; import java.awt.BorderLayout; - import javax.swing.JInternalFrame; import javax.swing.JPanel; -public class GameWindow extends JInternalFrame -{ - private final GameVisualizer m_visualizer; - public GameWindow() - { +public class GameWindow extends JInternalFrame { + public GameWindow(RobotModel model) { super("Игровое поле", true, true, true, true); - m_visualizer = new GameVisualizer(); + GameVisualizer visualizer = new GameVisualizer(model); JPanel panel = new JPanel(new BorderLayout()); - panel.add(m_visualizer, BorderLayout.CENTER); + panel.add(visualizer, BorderLayout.CENTER); getContentPane().add(panel); pack(); } diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 3f4da43..ee969f4 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -10,6 +10,8 @@ import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; + +import config.ConfigManager; import log.Logger; public class MainApplicationFrame extends JFrame { @@ -20,24 +22,40 @@ public class MainApplicationFrame extends JFrame { private final JDesktopPane desktopPane = new JDesktopPane(); private final ConfigManager configManager = new ConfigManager(); - + private final RobotModel robotModel; + private final RobotController robotController; + private final javax.swing.Timer timer; public MainApplicationFrame() { configManager.load(); loadMainWindowState(); + robotModel = new RobotModel(); + robotController = new RobotController(robotModel); + setContentPane(desktopPane); LogWindow logWindow = createLogWindow(); addWindow(logWindow); - GameWindow gameWindow = new GameWindow(); + + GameWindow gameWindow = new GameWindow(robotModel); gameWindow.setSize(400, 400); addWindow(gameWindow); + RobotCoordinatesWindow coordWindow = new RobotCoordinatesWindow(robotModel); + coordWindow.setSize(250, 100); + coordWindow.setLocation(10, 400); + addWindow(coordWindow); + loadInternalWindowState(LOG_WINDOW_NAME, logWindow); loadInternalWindowState(GAME_WINDOW_NAME, gameWindow); + loadInternalWindowState("CoordWindow", coordWindow); setJMenuBar(new MenuBarFactory(this).createMenuBar()); + // Таймер для обновления модели (каждые 10 мс) + timer = new javax.swing.Timer(10, e -> robotController.updateModel()); + timer.start(); + addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { @@ -47,6 +65,7 @@ public void windowClosing(WindowEvent e) { } public void exitApplication() { + timer.stop(); saveAllWindowsState(); configManager.save(); diff --git a/robots/src/gui/RobotController.java b/robots/src/gui/RobotController.java new file mode 100644 index 0000000..8d4368a --- /dev/null +++ b/robots/src/gui/RobotController.java @@ -0,0 +1,47 @@ +package gui; + +public class RobotController { + private final RobotModel model; + private final double maxVelocity = 0.1; + private final double maxAngularVelocity = 0.001; + + public RobotController(RobotModel model) { + this.model = model; + } + + public void updateModel() { + double dx = model.getTargetX() - model.getRobotX(); + double dy = model.getTargetY() - model.getRobotY(); + double distance = Math.hypot(dx, dy); + if (distance < 0.5) { + return; // цель достигнута + } + + double angleToTarget = normalizeRadians(Math.atan2(dy, dx)); + double robotDir = model.getRobotDirection(); + + // Вычисляем кратчайшую разницу углов + double diff = angleToTarget - robotDir; + diff = normalizeRadians(diff); + if (diff > Math.PI) diff -= 2 * Math.PI; + if (diff < -Math.PI) diff += 2 * Math.PI; + + double angularVelocity; + if (Math.abs(diff) < 0.01) { + angularVelocity = 0; + } else { + angularVelocity = (diff > 0) ? maxAngularVelocity : -maxAngularVelocity; + } + + // Если робот смотрит почти на цель – едем, иначе только поворачиваем + double velocity = (Math.abs(diff) < 0.3) ? maxVelocity : 0; + + model.update(velocity, angularVelocity, 10); // duration = 10 мс + } + + private double normalizeRadians(double angle) { + while (angle < 0) angle += 2 * Math.PI; + while (angle >= 2 * Math.PI) angle -= 2 * Math.PI; + return angle; + } +} diff --git a/robots/src/gui/RobotCoordinatesWindow.java b/robots/src/gui/RobotCoordinatesWindow.java new file mode 100644 index 0000000..6657d29 --- /dev/null +++ b/robots/src/gui/RobotCoordinatesWindow.java @@ -0,0 +1,43 @@ +package gui; + +import java.awt.GridLayout; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javax.swing.JInternalFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +public class RobotCoordinatesWindow extends JInternalFrame implements PropertyChangeListener { + private final JLabel positionLabel; + private final JLabel directionLabel; + private final RobotModel model; + + public RobotCoordinatesWindow(RobotModel model) { + super("Координаты робота", true, true, true, true); + this.model = model; + model.addPropertyChangeListener(this); + + JPanel panel = new JPanel(new GridLayout(2, 1)); + positionLabel = new JLabel(); + directionLabel = new JLabel(); + panel.add(positionLabel); + panel.add(directionLabel); + getContentPane().add(panel); + pack(); + setSize(200, 80); + updateLabels(); + } + + private void updateLabels() { + positionLabel.setText(String.format("Позиция: (%.1f, %.1f)", model.getRobotX(), model.getRobotY())); + directionLabel.setText(String.format("Направление: %.1f°", Math.toDegrees(model.getRobotDirection()))); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (RobotModel.PROP_POSITION.equals(evt.getPropertyName()) || + RobotModel.PROP_DIRECTION.equals(evt.getPropertyName())) { + updateLabels(); + } + } +} diff --git a/robots/src/gui/RobotModel.java b/robots/src/gui/RobotModel.java new file mode 100644 index 0000000..64e7589 --- /dev/null +++ b/robots/src/gui/RobotModel.java @@ -0,0 +1,90 @@ +package gui; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +public class RobotModel { + public static final String PROP_POSITION = "position"; + public static final String PROP_DIRECTION = "direction"; + public static final String PROP_TARGET = "target"; + + private volatile double robotX = 100; + private volatile double robotY = 100; + private volatile double robotDirection = 0; + private volatile int targetX = 150; + private volatile int targetY = 100; + + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + + public void addPropertyChangeListener(PropertyChangeListener listener) { + pcs.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + pcs.removePropertyChangeListener(listener); + } + + public double getRobotX() { return robotX; } + public double getRobotY() { return robotY; } + public double getRobotDirection() { return robotDirection; } + public int getTargetX() { return targetX; } + public int getTargetY() { return targetY; } + + public void setTarget(int x, int y) { + int oldX = targetX; + int oldY = targetY; + targetX = x; + targetY = y; + pcs.firePropertyChange(PROP_TARGET, null, new java.awt.Point(x, y)); + } + + public void setRobotPosition(double x, double y) { + double oldX = robotX; + double oldY = robotY; + robotX = x; + robotY = y; + pcs.firePropertyChange(PROP_POSITION, null, new java.awt.Point((int)x, (int)y)); + } + + public void setRobotDirection(double direction) { + double oldDir = robotDirection; + robotDirection = normalizeRadians(direction); + pcs.firePropertyChange(PROP_DIRECTION, oldDir, robotDirection); + } + + // Метод обновления модели по законам физики + public void update(double velocity, double angularVelocity, double duration) { + velocity = applyLimits(velocity, 0, maxVelocity); + angularVelocity = applyLimits(angularVelocity, -maxAngularVelocity, maxAngularVelocity); + + double newX, newY; + if (Math.abs(angularVelocity) < 1e-8) { + newX = robotX + velocity * duration * Math.cos(robotDirection); + newY = robotY + velocity * duration * Math.sin(robotDirection); + } else { + newX = robotX + (velocity / angularVelocity) * + (Math.sin(robotDirection + angularVelocity * duration) - Math.sin(robotDirection)); + newY = robotY - (velocity / angularVelocity) * + (Math.cos(robotDirection + angularVelocity * duration) - Math.cos(robotDirection)); + } + double newDirection = normalizeRadians(robotDirection + angularVelocity * duration); + + setRobotPosition(newX, newY); + setRobotDirection(newDirection); + } + + private double applyLimits(double value, double min, double max) { + if (value < min) return min; + if (value > max) return max; + return value; + } + + private double normalizeRadians(double angle) { + while (angle < 0) angle += 2 * Math.PI; + while (angle >= 2 * Math.PI) angle -= 2 * Math.PI; + return angle; + } + + private static final double maxVelocity = 0.1; + private static final double maxAngularVelocity = 0.001; +} From a7d2d379bb80c1df59d0b3ef4ac9f730c7464434 Mon Sep 17 00:00:00 2001 From: Alla509 Date: Tue, 21 Apr 2026 14:19:01 +0500 Subject: [PATCH 07/14] implement bounded concurrent log buffer with O(1) add and snapshot iterators --- robots/src/log/LogBuffer.java | 90 ++++++++++++++++++++++++++ robots/src/log/LogWindowSource.java | 98 +++++++++++++---------------- 2 files changed, 135 insertions(+), 53 deletions(-) create mode 100644 robots/src/log/LogBuffer.java diff --git a/robots/src/log/LogBuffer.java b/robots/src/log/LogBuffer.java new file mode 100644 index 0000000..448eb7e --- /dev/null +++ b/robots/src/log/LogBuffer.java @@ -0,0 +1,90 @@ +package log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Потокобезопасный кольцевой буфер с ограниченной ёмкостью. + * Старые записи автоматически вытесняются при переполнении. + */ +public class LogBuffer { + private final int capacity; + private final LogEntry[] buffer; + private int head; // индекс самой старой записи + private int size; // текущее количество записей + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + public LogBuffer(int capacity) { + if (capacity <= 0) throw new IllegalArgumentException("Capacity must be positive"); + this.capacity = capacity; + this.buffer = new LogEntry[capacity]; + } + + /** + * Добавляет запись. При переполнении самая старая запись перезаписывается. + */ + public void add(LogEntry entry) { + lock.writeLock().lock(); + try { + if (size < capacity) { + buffer[(head + size) % capacity] = entry; + size++; + } else { + buffer[head] = entry; + head = (head + 1) % capacity; + } + } finally { + lock.writeLock().unlock(); + } + } + + /** + * Возвращает текущее количество записей. + */ + public int size() { + lock.readLock().lock(); + try { + return size; + } finally { + lock.readLock().unlock(); + } + } + + /** + * Возвращает снимок всех записей в порядке от старых к новым. + */ + public List getAll() { + lock.readLock().lock(); + try { + List snapshot = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + snapshot.add(buffer[(head + i) % capacity]); + } + return Collections.unmodifiableList(snapshot); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Возвращает снимок подсписка записей от startIndex (включительно) до endIndex (исключительно). + * Индексы соответствуют позициям в порядке от старых к новым. + */ + public List getRange(int startIndex, int endIndex) { + if (startIndex < 0 || endIndex > size || startIndex > endIndex) { + throw new IndexOutOfBoundsException("Invalid range: " + startIndex + ".." + endIndex); + } + lock.readLock().lock(); + try { + List range = new ArrayList<>(endIndex - startIndex); + for (int i = startIndex; i < endIndex; i++) { + range.add(buffer[(head + i) % capacity]); + } + return Collections.unmodifiableList(range); + } finally { + lock.readLock().unlock(); + } + } +} diff --git a/robots/src/log/LogWindowSource.java b/robots/src/log/LogWindowSource.java index ca0ce44..b31f732 100644 --- a/robots/src/log/LogWindowSource.java +++ b/robots/src/log/LogWindowSource.java @@ -2,88 +2,80 @@ import java.util.ArrayList; import java.util.Collections; - /** * Что починить: * 1. Этот класс порождает утечку ресурсов (связанные слушатели оказываются * удерживаемыми в памяти) - * 2. Этот класс хранит активные сообщения лога, но в такой реализации он - * их лишь накапливает. Надо же, чтобы количество сообщений в логе было ограничено - * величиной m_iQueueLength (т.е. реально нужна очередь сообщений - * ограниченного размера) + * 2. Этот класс хранит активные сообщения лога, но в такой реализации он + * их лишь накапливает. Надо же, чтобы количество сообщений в логе было ограничено + * величиной m_iQueueLength (т.е. реально нужна очередь сообщений + * ограниченного размера) */ -public class LogWindowSource -{ - private int m_iQueueLength; - - private ArrayList m_messages; +public class LogWindowSource { + private final LogBuffer logBuffer; // ограниченная очередь private final ArrayList m_listeners; private volatile LogChangeListener[] m_activeListeners; - - public LogWindowSource(int iQueueLength) - { - m_iQueueLength = iQueueLength; - m_messages = new ArrayList(iQueueLength); - m_listeners = new ArrayList(); + + public LogWindowSource(int iQueueLength) { + logBuffer = new LogBuffer(iQueueLength); + m_listeners = new ArrayList<>(); } - - public void registerListener(LogChangeListener listener) - { - synchronized(m_listeners) - { + + public void registerListener(LogChangeListener listener) { + synchronized (m_listeners) { m_listeners.add(listener); m_activeListeners = null; } } - - public void unregisterListener(LogChangeListener listener) - { - synchronized(m_listeners) - { + + public void unregisterListener(LogChangeListener listener) { + synchronized (m_listeners) { m_listeners.remove(listener); m_activeListeners = null; } } - - public void append(LogLevel logLevel, String strMessage) - { + + public void append(LogLevel logLevel, String strMessage) { LogEntry entry = new LogEntry(logLevel, strMessage); - m_messages.add(entry); - LogChangeListener [] activeListeners = m_activeListeners; - if (activeListeners == null) - { - synchronized (m_listeners) - { - if (m_activeListeners == null) - { - activeListeners = m_listeners.toArray(new LogChangeListener [0]); + logBuffer.add(entry); + + LogChangeListener[] activeListeners = m_activeListeners; + if (activeListeners == null) { + synchronized (m_listeners) { + if (m_activeListeners == null) { + activeListeners = m_listeners.toArray(new LogChangeListener[0]); m_activeListeners = activeListeners; } } } - for (LogChangeListener listener : activeListeners) - { + for (LogChangeListener listener : activeListeners) { listener.onLogChanged(); } } - - public int size() - { - return m_messages.size(); + + public int size() { + return logBuffer.size(); } - public Iterable range(int startFrom, int count) - { - if (startFrom < 0 || startFrom >= m_messages.size()) - { + /** + * Возвращает диапазон записей от startFrom (включительно) в количестве count. + * @param startFrom индекс первой записи (0 – самая старая) + * @param count количество записей + * @return неизменяемый список записей (может быть меньше count, если достигнут конец) + */ + public Iterable range(int startFrom, int count) { + int total = size(); + if (startFrom < 0 || startFrom >= total) { return Collections.emptyList(); } - int indexTo = Math.min(startFrom + count, m_messages.size()); - return m_messages.subList(startFrom, indexTo); + int endIndex = Math.min(startFrom + count, total); + return logBuffer.getRange(startFrom, endIndex); } - public Iterable all() - { - return m_messages; + /** + * Возвращает все записи в виде снимка. + */ + public Iterable all() { + return logBuffer.getAll(); } } From 4416561f43afa72d5306ae9025aa7265f577512e Mon Sep 17 00:00:00 2001 From: Alla509 Date: Thu, 23 Apr 2026 21:09:36 +0500 Subject: [PATCH 08/14] new package model with classes: RobotModel (full logic), RobotController (use methods from RobotModel and other classes in package), RobotConstants, RobotMath new class WindowStateManager saves state of all windows, work with ConfigManager --- robots/src/config/ConfigManager.java | 33 ++--- robots/src/gui/GameVisualizer.java | 18 ++- robots/src/gui/GameWindow.java | 6 +- robots/src/gui/MainApplicationFrame.java | 154 +++++++++------------ robots/src/gui/MenuBarFactory.java | 122 ++++++---------- robots/src/gui/RobotController.java | 47 ------- robots/src/gui/RobotCoordinatesWindow.java | 15 +- robots/src/gui/RobotsProgram.java | 2 - robots/src/gui/WindowStateManager.java | 97 +++++++++++++ robots/src/model/RobotConstants.java | 8 ++ robots/src/model/RobotController.java | 31 +++++ robots/src/model/RobotMath.java | 17 +++ robots/src/{gui => model}/RobotModel.java | 45 ++---- 13 files changed, 301 insertions(+), 294 deletions(-) delete mode 100644 robots/src/gui/RobotController.java create mode 100644 robots/src/gui/WindowStateManager.java create mode 100644 robots/src/model/RobotConstants.java create mode 100644 robots/src/model/RobotController.java create mode 100644 robots/src/model/RobotMath.java rename robots/src/{gui => model}/RobotModel.java (59%) diff --git a/robots/src/config/ConfigManager.java b/robots/src/config/ConfigManager.java index 06c5d42..a4b0b86 100644 --- a/robots/src/config/ConfigManager.java +++ b/robots/src/config/ConfigManager.java @@ -7,10 +7,6 @@ import java.util.Properties; import log.Logger; -/** - * Управление сохранением и загрузкой конфигурации приложения. - * Файл хранится в домашнем каталоге пользователя. - */ public class ConfigManager { private static final String CONFIG_FILE = System.getProperty("user.home") + File.separator + ".robot-config.properties"; private final Properties props = new Properties(); @@ -67,30 +63,27 @@ public boolean hasWindow(String windowName) { public boolean getWindowIcon(String windowName, boolean defaultValue) { return getBooleanProperty(key(windowName, "icon"), defaultValue); } public boolean getWindowMaximized(String windowName, boolean defaultValue) { return getBooleanProperty(key(windowName, "maximized"), defaultValue); } - private String key(String windowName, String suffix) { - return windowName + "." + suffix; // единственное место с конкатенацией (инкапсулировано) + public void setWindowVisible(String windowName, boolean visible) { + setProperty(key(windowName, "visible"), visible); } - private void setProperty(String key, int value) { - props.setProperty(key, Integer.toString(value)); + public boolean getWindowVisible(String windowName, boolean defaultValue) { + return getBooleanProperty(key(windowName, "visible"), defaultValue); } - private void setProperty(String key, boolean value) { - props.setProperty(key, Boolean.toString(value)); - } + + private String key(String windowName, String suffix) { return windowName + "." + suffix; } + private void setProperty(String key, int value) { props.setProperty(key, Integer.toString(value)); } + private void setProperty(String key, boolean value) { props.setProperty(key, Boolean.toString(value)); } private int getIntProperty(String key, int defaultValue) { - String value = props.getProperty(key); - if (value == null) return defaultValue; - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - return defaultValue; - } + String val = props.getProperty(key); + if (val == null) return defaultValue; + try { return Integer.parseInt(val); } catch (NumberFormatException e) { return defaultValue; } } private boolean getBooleanProperty(String key, boolean defaultValue) { - String value = props.getProperty(key); - return value == null ? defaultValue : Boolean.parseBoolean(value); + String val = props.getProperty(key); + return val == null ? defaultValue : Boolean.parseBoolean(val); } } diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java index d8803bb..0c3c812 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/robots/src/gui/GameVisualizer.java @@ -1,14 +1,13 @@ package gui; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; +import model.RobotModel; +import javax.swing.*; +import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import javax.swing.JPanel; public class GameVisualizer extends JPanel implements PropertyChangeListener { private final RobotModel model; @@ -16,11 +15,10 @@ public class GameVisualizer extends JPanel implements PropertyChangeListener { public GameVisualizer(RobotModel model) { this.model = model; model.addPropertyChangeListener(this); - addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - model.setTarget(e.getPoint().x, e.getPoint().y); + model.setTarget(e.getX(), e.getY()); } }); setDoubleBuffered(true); @@ -61,11 +59,11 @@ private void drawTarget(Graphics2D g, int x, int y) { drawOval(g, x, y, 5, 5); } - private void fillOval(Graphics g, int centerX, int centerY, int diam1, int diam2) { - g.fillOval(centerX - diam1/2, centerY - diam2/2, diam1, diam2); + private void fillOval(Graphics g, int cx, int cy, int w, int h) { + g.fillOval(cx - w/2, cy - h/2, w, h); } - private void drawOval(Graphics g, int centerX, int centerY, int diam1, int diam2) { - g.drawOval(centerX - diam1/2, centerY - diam2/2, diam1, diam2); + private void drawOval(Graphics g, int cx, int cy, int w, int h) { + g.drawOval(cx - w/2, cy - h/2, w, h); } } diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java index 51081cd..1cc1de1 100644 --- a/robots/src/gui/GameWindow.java +++ b/robots/src/gui/GameWindow.java @@ -1,8 +1,8 @@ package gui; -import java.awt.BorderLayout; -import javax.swing.JInternalFrame; -import javax.swing.JPanel; +import model.RobotModel; +import javax.swing.*; +import java.awt.*; public class GameWindow extends JInternalFrame { public GameWindow(RobotModel model) { diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index ee969f4..d3f8f20 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -1,59 +1,57 @@ package gui; -import java.awt.Dimension; -import java.awt.Toolkit; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import javax.swing.JDesktopPane; -import javax.swing.JFrame; -import javax.swing.JInternalFrame; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; - import config.ConfigManager; +import model.RobotModel; +import model.RobotController; import log.Logger; +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + public class MainApplicationFrame extends JFrame { private static final String LOG_WINDOW_TITLE = "Протокол работы"; private static final String GAME_WINDOW_TITLE = "Игровое поле"; + private static final String COORD_WINDOW_TITLE = "Координаты робота"; private static final String LOG_WINDOW_NAME = "LogWindow"; private static final String GAME_WINDOW_NAME = "GameWindow"; + private static final String COORD_WINDOW_NAME = "CoordWindow"; private final JDesktopPane desktopPane = new JDesktopPane(); private final ConfigManager configManager = new ConfigManager(); + private final WindowStateManager windowStateManager = new WindowStateManager(configManager); + private final RobotModel robotModel; private final RobotController robotController; - private final javax.swing.Timer timer; + private final Timer timer; + public MainApplicationFrame() { configManager.load(); - loadMainWindowState(); + windowStateManager.loadMainWindowState(this); + setContentPane(desktopPane); robotModel = new RobotModel(); robotController = new RobotController(robotModel); - setContentPane(desktopPane); - LogWindow logWindow = createLogWindow(); - addWindow(logWindow); - GameWindow gameWindow = new GameWindow(robotModel); - gameWindow.setSize(400, 400); - addWindow(gameWindow); - RobotCoordinatesWindow coordWindow = new RobotCoordinatesWindow(robotModel); + + addWindow(LOG_WINDOW_NAME, logWindow); + gameWindow.setSize(400, 400); + addWindow(GAME_WINDOW_NAME, gameWindow); coordWindow.setSize(250, 100); coordWindow.setLocation(10, 400); - addWindow(coordWindow); + addWindow(COORD_WINDOW_NAME, coordWindow); - loadInternalWindowState(LOG_WINDOW_NAME, logWindow); - loadInternalWindowState(GAME_WINDOW_NAME, gameWindow); - loadInternalWindowState("CoordWindow", coordWindow); + windowStateManager.loadInternalWindowState(logWindow, LOG_WINDOW_NAME); + windowStateManager.loadInternalWindowState(gameWindow, GAME_WINDOW_NAME); + windowStateManager.loadInternalWindowState(coordWindow, COORD_WINDOW_NAME); setJMenuBar(new MenuBarFactory(this).createMenuBar()); - // Таймер для обновления модели (каждые 10 мс) - timer = new javax.swing.Timer(10, e -> robotController.updateModel()); + timer = new Timer(10, e -> robotController.updateModel()); timer.start(); addWindowListener(new WindowAdapter() { @@ -64,22 +62,27 @@ public void windowClosing(WindowEvent e) { }); } - public void exitApplication() { - timer.stop(); - saveAllWindowsState(); - configManager.save(); + private void addWindow(String windowName, JInternalFrame frame) { + windowStateManager.setupWindowBehavior(frame, windowName); + desktopPane.add(frame); + } - int result = JOptionPane.showConfirmDialog(this, - "Вы действительно хотите выйти из приложения?", - "Подтверждение выхода", - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE); - if (result == JOptionPane.YES_OPTION) { - System.exit(0); + public void showWindowByName(String windowName) { + for (JInternalFrame frame : desktopPane.getAllFrames()) { + if (windowName.equals(getWindowNameByTitle(frame.getTitle()))) { + frame.setVisible(true); + try { + if (frame.isIcon()) frame.setIcon(false); + } catch (java.beans.PropertyVetoException e) { + Logger.debug("Property veto exception"); + } + frame.toFront(); + break; + } } } - protected LogWindow createLogWindow() { + private LogWindow createLogWindow() { LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); logWindow.setLocation(10, 10); logWindow.setSize(300, 800); @@ -89,9 +92,26 @@ protected LogWindow createLogWindow() { return logWindow; } - protected void addWindow(JInternalFrame frame) { - desktopPane.add(frame); - frame.setVisible(true); + public void exitApplication() { + if (timer != null && timer.isRunning()) timer.stop(); + + windowStateManager.saveMainWindowState(this); + for (JInternalFrame frame : desktopPane.getAllFrames()) { + String name = getWindowNameByTitle(frame.getTitle()); + if (name != null) { + windowStateManager.saveInternalWindowState(frame, name); + } + } + configManager.save(); + + int result = JOptionPane.showConfirmDialog(this, + "Вы действительно хотите выйти из приложения?", + "Подтверждение выхода", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (result == JOptionPane.YES_OPTION) { + System.exit(0); + } } public void setLookAndFeel(String className) { @@ -100,62 +120,14 @@ public void setLookAndFeel(String className) { RobotsProgram.setRussianUIManagerText(); SwingUtilities.updateComponentTreeUI(this); } catch (Exception e) { - // ignore - } - } - - private void loadMainWindowState() { - int inset = 50; - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - int defaultWidth = screenSize.width - inset * 2; - int defaultHeight = screenSize.height - inset * 2; - - setBounds(configManager.getMainWindowX(inset), - configManager.getMainWindowY(inset), - configManager.getMainWindowWidth(defaultWidth), - configManager.getMainWindowHeight(defaultHeight)); - setExtendedState(configManager.getMainWindowState(JFrame.NORMAL)); - } - - private void loadInternalWindowState(String windowName, JInternalFrame frame) { - if (!configManager.hasWindow(windowName)) return; - - int x = configManager.getWindowX(windowName, frame.getX()); - int y = configManager.getWindowY(windowName, frame.getY()); - int w = configManager.getWindowWidth(windowName, frame.getWidth()); - int h = configManager.getWindowHeight(windowName, frame.getHeight()); - boolean icon = configManager.getWindowIcon(windowName, false); - boolean maximized = configManager.getWindowMaximized(windowName, false); - - frame.setBounds(x, y, w, h); - try { - if (maximized) { - frame.setMaximum(true); - } else if (icon) { - frame.setIcon(true); - } - } catch (java.beans.PropertyVetoException e) { - Logger.debug("Не удалось восстановить состояние окна " + windowName + ": " + e.getMessage()); - } - } - - private void saveAllWindowsState() { - configManager.setMainWindowBounds(getX(), getY(), getWidth(), getHeight(), getExtendedState()); - - for (JInternalFrame frame : desktopPane.getAllFrames()) { - String windowName = getWindowNameByTitle(frame.getTitle()); - if (windowName != null) { - configManager.setWindowBounds(windowName, - frame.getX(), frame.getY(), - frame.getWidth(), frame.getHeight(), - frame.isIcon(), frame.isMaximum()); - } + Logger.debug("Exception"); } } private String getWindowNameByTitle(String title) { if (LOG_WINDOW_TITLE.equals(title)) return LOG_WINDOW_NAME; if (GAME_WINDOW_TITLE.equals(title)) return GAME_WINDOW_NAME; + if (COORD_WINDOW_TITLE.equals(title)) return COORD_WINDOW_NAME; return null; } } diff --git a/robots/src/gui/MenuBarFactory.java b/robots/src/gui/MenuBarFactory.java index c286689..ca64338 100644 --- a/robots/src/gui/MenuBarFactory.java +++ b/robots/src/gui/MenuBarFactory.java @@ -1,123 +1,85 @@ package gui; -import java.awt.event.KeyEvent; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.UIManager; import log.Logger; -/** - * Фабрика для создания строки меню приложения - */ -public class MenuBarFactory { +import javax.swing.*; +import java.awt.event.KeyEvent; +public class MenuBarFactory { private final MainApplicationFrame frame; public MenuBarFactory(MainApplicationFrame frame) { this.frame = frame; } - /** - * Создает главную строку меню - */ public JMenuBar createMenuBar() { JMenuBar menuBar = new JMenuBar(); - menuBar.add(createFileMenu()); menuBar.add(createLookAndFeelMenu()); menuBar.add(createTestMenu()); - + menuBar.add(createWindowsMenu()); return menuBar; } - /** - * Создает меню "Файл" - */ private JMenu createFileMenu() { JMenu fileMenu = new JMenu("Файл"); fileMenu.setMnemonic(KeyEvent.VK_F); - fileMenu.getAccessibleContext().setAccessibleDescription( - "Управление файлами и приложением"); - - fileMenu.add(createExitMenuItem()); - + JMenuItem exitItem = new JMenuItem("Выход", KeyEvent.VK_X); + exitItem.addActionListener(e -> frame.exitApplication()); + fileMenu.add(exitItem); return fileMenu; } - /** - * Создает пункт меню для выхода из приложения - */ - private JMenuItem createExitMenuItem() { - JMenuItem exitMenuItem = new JMenuItem("Выход", KeyEvent.VK_X); - exitMenuItem.addActionListener((event) -> { - frame.exitApplication(); - }); - return exitMenuItem; - } - - - /** - * Создает меню "Режим отображения" - */ private JMenu createLookAndFeelMenu() { - JMenu lookAndFeelMenu = new JMenu("Режим отображения"); - lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); - lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( - "Управление режимом отображения приложения"); - - lookAndFeelMenu.add(createSystemLookAndFeelItem()); - lookAndFeelMenu.add(createCrossPlatformLookAndFeelItem()); - - return lookAndFeelMenu; - } + JMenu menu = new JMenu("Режим отображения"); + menu.setMnemonic(KeyEvent.VK_V); - /** - * Создает пункт меню для системной схемы оформления - */ - private JMenuItem createSystemLookAndFeelItem() { - JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); - systemLookAndFeel.addActionListener((event) -> { + JMenuItem system = new JMenuItem("Системная схема"); + system.addActionListener(e -> { frame.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); frame.invalidate(); }); - return systemLookAndFeel; - } - /** - * Создает пункт меню для универсальной схемы оформления - */ - private JMenuItem createCrossPlatformLookAndFeelItem() { - JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_S); - crossplatformLookAndFeel.addActionListener((event) -> { + JMenuItem cross = new JMenuItem("Универсальная схема"); + cross.addActionListener(e -> { frame.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); frame.invalidate(); }); - return crossplatformLookAndFeel; + + menu.add(system); + menu.add(cross); + return menu; } - /** - * Создает меню "Тесты" - */ private JMenu createTestMenu() { - JMenu testMenu = new JMenu("Тесты"); - testMenu.setMnemonic(KeyEvent.VK_T); - testMenu.getAccessibleContext().setAccessibleDescription( - "Тестовые команды"); + JMenu menu = new JMenu("Тесты"); + menu.setMnemonic(KeyEvent.VK_T); + JMenuItem logMsg = new JMenuItem("Сообщение в лог"); + logMsg.addActionListener(e -> Logger.debug("Новая строка")); + menu.add(logMsg); + return menu; + } + + private JMenu createWindowsMenu() { + JMenu menu = new JMenu("Окна"); + menu.setMnemonic(KeyEvent.VK_W); + + JMenuItem showLog = new JMenuItem("Показать протокол"); + showLog.addActionListener(e -> showWindow("LogWindow")); - testMenu.add(createAddLogMessageItem()); + JMenuItem showGame = new JMenuItem("Показать игровое поле"); + showGame.addActionListener(e -> showWindow("GameWindow")); - return testMenu; + JMenuItem showCoord = new JMenuItem("Показать координаты"); + showCoord.addActionListener(e -> showWindow("CoordWindow")); + + menu.add(showLog); + menu.add(showGame); + menu.add(showCoord); + return menu; } - /** - * Создает пункт меню для добавления сообщения в лог - */ - private JMenuItem createAddLogMessageItem() { - JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); - addLogMessageItem.addActionListener((event) -> { - Logger.debug("Новая строка"); - }); - return addLogMessageItem; + private void showWindow(String windowName) { + frame.showWindowByName(windowName); } } diff --git a/robots/src/gui/RobotController.java b/robots/src/gui/RobotController.java deleted file mode 100644 index 8d4368a..0000000 --- a/robots/src/gui/RobotController.java +++ /dev/null @@ -1,47 +0,0 @@ -package gui; - -public class RobotController { - private final RobotModel model; - private final double maxVelocity = 0.1; - private final double maxAngularVelocity = 0.001; - - public RobotController(RobotModel model) { - this.model = model; - } - - public void updateModel() { - double dx = model.getTargetX() - model.getRobotX(); - double dy = model.getTargetY() - model.getRobotY(); - double distance = Math.hypot(dx, dy); - if (distance < 0.5) { - return; // цель достигнута - } - - double angleToTarget = normalizeRadians(Math.atan2(dy, dx)); - double robotDir = model.getRobotDirection(); - - // Вычисляем кратчайшую разницу углов - double diff = angleToTarget - robotDir; - diff = normalizeRadians(diff); - if (diff > Math.PI) diff -= 2 * Math.PI; - if (diff < -Math.PI) diff += 2 * Math.PI; - - double angularVelocity; - if (Math.abs(diff) < 0.01) { - angularVelocity = 0; - } else { - angularVelocity = (diff > 0) ? maxAngularVelocity : -maxAngularVelocity; - } - - // Если робот смотрит почти на цель – едем, иначе только поворачиваем - double velocity = (Math.abs(diff) < 0.3) ? maxVelocity : 0; - - model.update(velocity, angularVelocity, 10); // duration = 10 мс - } - - private double normalizeRadians(double angle) { - while (angle < 0) angle += 2 * Math.PI; - while (angle >= 2 * Math.PI) angle -= 2 * Math.PI; - return angle; - } -} diff --git a/robots/src/gui/RobotCoordinatesWindow.java b/robots/src/gui/RobotCoordinatesWindow.java index 6657d29..3049021 100644 --- a/robots/src/gui/RobotCoordinatesWindow.java +++ b/robots/src/gui/RobotCoordinatesWindow.java @@ -1,11 +1,10 @@ package gui; -import java.awt.GridLayout; +import model.RobotModel; +import javax.swing.*; +import java.awt.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import javax.swing.JInternalFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; public class RobotCoordinatesWindow extends JInternalFrame implements PropertyChangeListener { private final JLabel positionLabel; @@ -23,8 +22,7 @@ public RobotCoordinatesWindow(RobotModel model) { panel.add(positionLabel); panel.add(directionLabel); getContentPane().add(panel); - pack(); - setSize(200, 80); + setSize(250, 80); updateLabels(); } @@ -35,9 +33,6 @@ private void updateLabels() { @Override public void propertyChange(PropertyChangeEvent evt) { - if (RobotModel.PROP_POSITION.equals(evt.getPropertyName()) || - RobotModel.PROP_DIRECTION.equals(evt.getPropertyName())) { - updateLabels(); - } + updateLabels(); } } diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java index 468cc39..d3447a4 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/robots/src/gui/RobotsProgram.java @@ -1,14 +1,12 @@ package gui; import java.awt.Frame; -import java.util.Locale; import javax.swing.SwingUtilities; import javax.swing.UIManager; public class RobotsProgram { public static void main(String[] args) { - Locale.setDefault(new Locale("ru", "RU")); setRussianUIManagerText(); try { UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); diff --git a/robots/src/gui/WindowStateManager.java b/robots/src/gui/WindowStateManager.java new file mode 100644 index 0000000..5f88d84 --- /dev/null +++ b/robots/src/gui/WindowStateManager.java @@ -0,0 +1,97 @@ +package gui; + +import config.ConfigManager; +import log.Logger; + +import javax.swing.*; +import javax.swing.event.InternalFrameAdapter; +import javax.swing.event.InternalFrameEvent; +import java.awt.*; + +public class WindowStateManager { + private final ConfigManager config; + + public WindowStateManager(ConfigManager config) { + this.config = config; + } + + public void loadMainWindowState(JFrame mainFrame) { + int inset = 50; + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int defaultW = screenSize.width - inset * 2; + int defaultH = screenSize.height - inset * 2; + + mainFrame.setBounds( + config.getMainWindowX(inset), + config.getMainWindowY(inset), + config.getMainWindowWidth(defaultW), + config.getMainWindowHeight(defaultH) + ); + mainFrame.setExtendedState(config.getMainWindowState(JFrame.NORMAL)); + } + + public void saveMainWindowState(JFrame mainFrame) { + config.setMainWindowBounds( + mainFrame.getX(), mainFrame.getY(), + mainFrame.getWidth(), mainFrame.getHeight(), + mainFrame.getExtendedState() + ); + } + + public void loadInternalWindowState(JInternalFrame frame, String windowName) { + int x = frame.getX(); + int y = frame.getY(); + int w = frame.getWidth(); + int h = frame.getHeight(); + boolean icon = false; + boolean maximized = false; + boolean visible = true; + + if (config.hasWindow(windowName)) { + x = config.getWindowX(windowName, x); + y = config.getWindowY(windowName, y); + w = config.getWindowWidth(windowName, w); + h = config.getWindowHeight(windowName, h); + icon = config.getWindowIcon(windowName, false); + maximized = config.getWindowMaximized(windowName, false); + visible = config.getWindowVisible(windowName, true); + } + + frame.setBounds(x, y, w, h); + try { + if (maximized) { + frame.setMaximum(true); + } else if (icon) { + frame.setIcon(true); + } + } catch (java.beans.PropertyVetoException e) { + Logger.debug("Property veto exception"); + } + frame.setVisible(visible); + } + + public void saveInternalWindowState(JInternalFrame frame, String windowName) { + config.setWindowBounds(windowName, + frame.getX(), frame.getY(), + frame.getWidth(), frame.getHeight(), + frame.isIcon(), frame.isMaximum() + ); + config.setWindowVisible(windowName, frame.isVisible()); + } + + public void setupWindowBehavior(JInternalFrame frame, String windowName) { + frame.setDefaultCloseOperation(JInternalFrame.HIDE_ON_CLOSE); + frame.addInternalFrameListener(new InternalFrameAdapter() { + @Override + public void internalFrameClosing(InternalFrameEvent e) { + config.setWindowVisible(windowName, false); + config.save(); + } + @Override + public void internalFrameOpened(InternalFrameEvent e) { + config.setWindowVisible(windowName, true); + config.save(); + } + }); + } +} diff --git a/robots/src/model/RobotConstants.java b/robots/src/model/RobotConstants.java new file mode 100644 index 0000000..1ca537b --- /dev/null +++ b/robots/src/model/RobotConstants.java @@ -0,0 +1,8 @@ +package model; + +public final class RobotConstants { + public static final double MAX_VELOCITY = 0.1; + public static final double MAX_ANGULAR_VELOCITY = 0.001; + + private RobotConstants() {} // запрет создания экземпляров +} diff --git a/robots/src/model/RobotController.java b/robots/src/model/RobotController.java new file mode 100644 index 0000000..26e56b6 --- /dev/null +++ b/robots/src/model/RobotController.java @@ -0,0 +1,31 @@ +package model; + +public class RobotController { + private final RobotModel model; + + public RobotController(RobotModel model) { + this.model = model; + } + + public void updateModel() { + double dx = model.getTargetX() - model.getRobotX(); + double dy = model.getTargetY() - model.getRobotY(); + double distance = Math.hypot(dx, dy); + if (distance < 0.5) return; + + double angleToTarget = RobotMath.normalizeRadians(Math.atan2(dy, dx)); + double robotDir = model.getRobotDirection(); + double diff = angleToTarget - robotDir; + diff = RobotMath.normalizeRadians(diff); + if (diff > Math.PI) diff -= 2 * Math.PI; + if (diff < -Math.PI) diff += 2 * Math.PI; + + double angularVelocity; + if (Math.abs(diff) < 0.01) angularVelocity = 0; + else angularVelocity = (diff > 0) ? RobotConstants.MAX_ANGULAR_VELOCITY : -RobotConstants.MAX_ANGULAR_VELOCITY; + + double velocity = (Math.abs(diff) < 0.3) ? RobotConstants.MAX_VELOCITY : 0; + + model.update(velocity, angularVelocity, 10); + } +} diff --git a/robots/src/model/RobotMath.java b/robots/src/model/RobotMath.java new file mode 100644 index 0000000..0f65127 --- /dev/null +++ b/robots/src/model/RobotMath.java @@ -0,0 +1,17 @@ +package model; + +public final class RobotMath { + private RobotMath() {} + + public static double applyLimits(double value, double min, double max) { + if (value < min) return min; + if (value > max) return max; + return value; + } + + public static double normalizeRadians(double angle) { + while (angle < 0) angle += 2 * Math.PI; + while (angle >= 2 * Math.PI) angle -= 2 * Math.PI; + return angle; + } +} diff --git a/robots/src/gui/RobotModel.java b/robots/src/model/RobotModel.java similarity index 59% rename from robots/src/gui/RobotModel.java rename to robots/src/model/RobotModel.java index 64e7589..4f718a1 100644 --- a/robots/src/gui/RobotModel.java +++ b/robots/src/model/RobotModel.java @@ -1,7 +1,8 @@ -package gui; +package model; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; +import java.awt.Point; public class RobotModel { public static final String PROP_POSITION = "position"; @@ -31,31 +32,28 @@ public void removePropertyChangeListener(PropertyChangeListener listener) { public int getTargetY() { return targetY; } public void setTarget(int x, int y) { - int oldX = targetX; - int oldY = targetY; targetX = x; targetY = y; - pcs.firePropertyChange(PROP_TARGET, null, new java.awt.Point(x, y)); + pcs.firePropertyChange(PROP_TARGET, null, new Point(x, y)); } - public void setRobotPosition(double x, double y) { - double oldX = robotX; - double oldY = robotY; + private void setRobotPosition(double x, double y) { robotX = x; robotY = y; - pcs.firePropertyChange(PROP_POSITION, null, new java.awt.Point((int)x, (int)y)); + pcs.firePropertyChange(PROP_POSITION, null, new Point((int)x, (int)y)); } - public void setRobotDirection(double direction) { - double oldDir = robotDirection; - robotDirection = normalizeRadians(direction); - pcs.firePropertyChange(PROP_DIRECTION, oldDir, robotDirection); + private void setRobotDirection(double direction) { + double old = robotDirection; + robotDirection = RobotMath.normalizeRadians(direction); + pcs.firePropertyChange(PROP_DIRECTION, old, robotDirection); } - // Метод обновления модели по законам физики + // Логика движения public void update(double velocity, double angularVelocity, double duration) { - velocity = applyLimits(velocity, 0, maxVelocity); - angularVelocity = applyLimits(angularVelocity, -maxAngularVelocity, maxAngularVelocity); + velocity = RobotMath.applyLimits(velocity, 0, RobotConstants.MAX_VELOCITY); + angularVelocity = RobotMath.applyLimits(angularVelocity, -RobotConstants.MAX_ANGULAR_VELOCITY, + RobotConstants.MAX_ANGULAR_VELOCITY); double newX, newY; if (Math.abs(angularVelocity) < 1e-8) { @@ -67,24 +65,9 @@ public void update(double velocity, double angularVelocity, double duration) { newY = robotY - (velocity / angularVelocity) * (Math.cos(robotDirection + angularVelocity * duration) - Math.cos(robotDirection)); } - double newDirection = normalizeRadians(robotDirection + angularVelocity * duration); + double newDirection = robotDirection + angularVelocity * duration; setRobotPosition(newX, newY); setRobotDirection(newDirection); } - - private double applyLimits(double value, double min, double max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - - private double normalizeRadians(double angle) { - while (angle < 0) angle += 2 * Math.PI; - while (angle >= 2 * Math.PI) angle -= 2 * Math.PI; - return angle; - } - - private static final double maxVelocity = 0.1; - private static final double maxAngularVelocity = 0.001; } From 4b4e3c7bd7810730709204c04bded794ff21cdee Mon Sep 17 00:00:00 2001 From: Alla509 Date: Fri, 8 May 2026 15:50:35 +0500 Subject: [PATCH 09/14] view interacts with controller; timer, setTarget in controller; constructor MainApplicationFrame gets dependencies from outside (RobotsProgram) --- robots/src/gui/GameVisualizer.java | 5 +-- robots/src/gui/GameWindow.java | 5 +-- robots/src/gui/MainApplicationFrame.java | 15 +++------ robots/src/gui/RobotsProgram.java | 6 +++- robots/src/model/RobotController.java | 30 +++++++---------- robots/src/model/RobotMath.java | 16 --------- robots/src/model/RobotModel.java | 42 +++++++++++++++++++++--- 7 files changed, 65 insertions(+), 54 deletions(-) diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java index 0c3c812..d38f107 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/robots/src/gui/GameVisualizer.java @@ -1,5 +1,6 @@ package gui; +import model.RobotController; import model.RobotModel; import javax.swing.*; import java.awt.*; @@ -12,13 +13,13 @@ public class GameVisualizer extends JPanel implements PropertyChangeListener { private final RobotModel model; - public GameVisualizer(RobotModel model) { + public GameVisualizer(RobotModel model, RobotController controller) { this.model = model; model.addPropertyChangeListener(this); addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - model.setTarget(e.getX(), e.getY()); + controller.setTarget(e.getX(), e.getY()); } }); setDoubleBuffered(true); diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java index 1cc1de1..c936533 100644 --- a/robots/src/gui/GameWindow.java +++ b/robots/src/gui/GameWindow.java @@ -1,13 +1,14 @@ package gui; +import model.RobotController; import model.RobotModel; import javax.swing.*; import java.awt.*; public class GameWindow extends JInternalFrame { - public GameWindow(RobotModel model) { + public GameWindow(RobotModel model, RobotController controller) { super("Игровое поле", true, true, true, true); - GameVisualizer visualizer = new GameVisualizer(model); + GameVisualizer visualizer = new GameVisualizer(model, controller); JPanel panel = new JPanel(new BorderLayout()); panel.add(visualizer, BorderLayout.CENTER); getContentPane().add(panel); diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index d3f8f20..cb9eb2f 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -22,20 +22,16 @@ public class MainApplicationFrame extends JFrame { private final ConfigManager configManager = new ConfigManager(); private final WindowStateManager windowStateManager = new WindowStateManager(configManager); - private final RobotModel robotModel; private final RobotController robotController; - private final Timer timer; - public MainApplicationFrame() { + public MainApplicationFrame(RobotModel robotModel, RobotController robotController) { + this.robotController = robotController; configManager.load(); windowStateManager.loadMainWindowState(this); setContentPane(desktopPane); - robotModel = new RobotModel(); - robotController = new RobotController(robotModel); - LogWindow logWindow = createLogWindow(); - GameWindow gameWindow = new GameWindow(robotModel); + GameWindow gameWindow = new GameWindow(robotModel, robotController); RobotCoordinatesWindow coordWindow = new RobotCoordinatesWindow(robotModel); addWindow(LOG_WINDOW_NAME, logWindow); @@ -51,9 +47,6 @@ public MainApplicationFrame() { setJMenuBar(new MenuBarFactory(this).createMenuBar()); - timer = new Timer(10, e -> robotController.updateModel()); - timer.start(); - addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { @@ -93,7 +86,7 @@ private LogWindow createLogWindow() { } public void exitApplication() { - if (timer != null && timer.isRunning()) timer.stop(); + if (robotController != null) robotController.stopTimer(); windowStateManager.saveMainWindowState(this); for (JInternalFrame frame : desktopPane.getAllFrames()) { diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java index d3447a4..18c1f58 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/robots/src/gui/RobotsProgram.java @@ -1,5 +1,7 @@ package gui; +import model.RobotModel; +import model.RobotController; import java.awt.Frame; import javax.swing.SwingUtilities; import javax.swing.UIManager; @@ -17,7 +19,9 @@ public static void main(String[] args) { e.printStackTrace(); } SwingUtilities.invokeLater(() -> { - MainApplicationFrame frame = new MainApplicationFrame(); + RobotModel model = new RobotModel(); + RobotController controller = new RobotController(model); + MainApplicationFrame frame = new MainApplicationFrame(model, controller); frame.pack(); frame.setVisible(true); frame.setExtendedState(Frame.MAXIMIZED_BOTH); diff --git a/robots/src/model/RobotController.java b/robots/src/model/RobotController.java index 26e56b6..d5dc0d6 100644 --- a/robots/src/model/RobotController.java +++ b/robots/src/model/RobotController.java @@ -1,31 +1,25 @@ package model; +import javax.swing.Timer; public class RobotController { private final RobotModel model; + private final Timer timer; public RobotController(RobotModel model) { this.model = model; + this.timer = new Timer(10, e -> updateModel()); + timer.start(); } - public void updateModel() { - double dx = model.getTargetX() - model.getRobotX(); - double dy = model.getTargetY() - model.getRobotY(); - double distance = Math.hypot(dx, dy); - if (distance < 0.5) return; - - double angleToTarget = RobotMath.normalizeRadians(Math.atan2(dy, dx)); - double robotDir = model.getRobotDirection(); - double diff = angleToTarget - robotDir; - diff = RobotMath.normalizeRadians(diff); - if (diff > Math.PI) diff -= 2 * Math.PI; - if (diff < -Math.PI) diff += 2 * Math.PI; - - double angularVelocity; - if (Math.abs(diff) < 0.01) angularVelocity = 0; - else angularVelocity = (diff > 0) ? RobotConstants.MAX_ANGULAR_VELOCITY : -RobotConstants.MAX_ANGULAR_VELOCITY; + public void setTarget(int x, int y) { + model.setTarget(x, y); + } - double velocity = (Math.abs(diff) < 0.3) ? RobotConstants.MAX_VELOCITY : 0; + public void stopTimer() { + if (timer.isRunning()) timer.stop(); + } - model.update(velocity, angularVelocity, 10); + public void updateModel() { + model.update( 10); } } diff --git a/robots/src/model/RobotMath.java b/robots/src/model/RobotMath.java index 0f65127..8b13789 100644 --- a/robots/src/model/RobotMath.java +++ b/robots/src/model/RobotMath.java @@ -1,17 +1 @@ -package model; -public final class RobotMath { - private RobotMath() {} - - public static double applyLimits(double value, double min, double max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - - public static double normalizeRadians(double angle) { - while (angle < 0) angle += 2 * Math.PI; - while (angle >= 2 * Math.PI) angle -= 2 * Math.PI; - return angle; - } -} diff --git a/robots/src/model/RobotModel.java b/robots/src/model/RobotModel.java index 4f718a1..517f604 100644 --- a/robots/src/model/RobotModel.java +++ b/robots/src/model/RobotModel.java @@ -45,14 +45,14 @@ private void setRobotPosition(double x, double y) { private void setRobotDirection(double direction) { double old = robotDirection; - robotDirection = RobotMath.normalizeRadians(direction); + robotDirection = normalizeRadians(direction); pcs.firePropertyChange(PROP_DIRECTION, old, robotDirection); } // Логика движения - public void update(double velocity, double angularVelocity, double duration) { - velocity = RobotMath.applyLimits(velocity, 0, RobotConstants.MAX_VELOCITY); - angularVelocity = RobotMath.applyLimits(angularVelocity, -RobotConstants.MAX_ANGULAR_VELOCITY, + private void update(double velocity, double angularVelocity, double duration) { + velocity = applyLimits(velocity, 0, RobotConstants.MAX_VELOCITY); + angularVelocity = applyLimits(angularVelocity, -RobotConstants.MAX_ANGULAR_VELOCITY, RobotConstants.MAX_ANGULAR_VELOCITY); double newX, newY; @@ -70,4 +70,38 @@ public void update(double velocity, double angularVelocity, double duration) { setRobotPosition(newX, newY); setRobotDirection(newDirection); } + + public void update(double duration) { + double dx = getTargetX() - getRobotX(); + double dy = getTargetY() - getRobotY(); + double distance = Math.hypot(dx, dy); + if (distance < 0.5) return; + + double angleToTarget = normalizeRadians(Math.atan2(dy, dx)); + double robotDir = getRobotDirection(); + double diff = angleToTarget - robotDir; + diff = normalizeRadians(diff); + if (diff > Math.PI) diff -= 2 * Math.PI; + if (diff < -Math.PI) diff += 2 * Math.PI; + + double angularVelocity; + if (Math.abs(diff) < 0.01) angularVelocity = 0; + else angularVelocity = (diff > 0) ? RobotConstants.MAX_ANGULAR_VELOCITY : -RobotConstants.MAX_ANGULAR_VELOCITY; + + double velocity = (Math.abs(diff) < 0.3) ? RobotConstants.MAX_VELOCITY : 0; + + update(velocity, angularVelocity, duration); + } + + private static double applyLimits(double value, double min, double max) { + if (value < min) return min; + if (value > max) return max; + return value; + } + + private static double normalizeRadians(double angle) { + while (angle < 0) angle += 2 * Math.PI; + while (angle >= 2 * Math.PI) angle -= 2 * Math.PI; + return angle; + } } From 127f7d8deeb8b822614f187dd7f994fe1725896e Mon Sep 17 00:00:00 2001 From: Alla509 Date: Thu, 14 May 2026 21:32:45 +0500 Subject: [PATCH 10/14] delete RobotMath.java; the timer period and model updates are set to a common constant --- robots/src/model/RobotController.java | 5 +++-- robots/src/model/RobotMath.java | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 robots/src/model/RobotMath.java diff --git a/robots/src/model/RobotController.java b/robots/src/model/RobotController.java index d5dc0d6..e3e0ae6 100644 --- a/robots/src/model/RobotController.java +++ b/robots/src/model/RobotController.java @@ -4,10 +4,11 @@ public class RobotController { private final RobotModel model; private final Timer timer; + private final int TIME_CONST = 10; public RobotController(RobotModel model) { this.model = model; - this.timer = new Timer(10, e -> updateModel()); + this.timer = new Timer(TIME_CONST, e -> updateModel()); timer.start(); } @@ -20,6 +21,6 @@ public void stopTimer() { } public void updateModel() { - model.update( 10); + model.update( TIME_CONST); } } diff --git a/robots/src/model/RobotMath.java b/robots/src/model/RobotMath.java deleted file mode 100644 index 8b13789..0000000 --- a/robots/src/model/RobotMath.java +++ /dev/null @@ -1 +0,0 @@ - From e0375e19a6272790ca9325450d0ffd32d287b734 Mon Sep 17 00:00:00 2001 From: Alla509 Date: Thu, 14 May 2026 21:57:44 +0500 Subject: [PATCH 11/14] delete unnecessary comments; push updated main --- robots/src/log/LogBuffer.java | 22 +++++----------------- robots/src/log/LogWindowSource.java | 21 +++------------------ 2 files changed, 8 insertions(+), 35 deletions(-) diff --git a/robots/src/log/LogBuffer.java b/robots/src/log/LogBuffer.java index 448eb7e..e945bbc 100644 --- a/robots/src/log/LogBuffer.java +++ b/robots/src/log/LogBuffer.java @@ -5,10 +5,7 @@ import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; -/** - * Потокобезопасный кольцевой буфер с ограниченной ёмкостью. - * Старые записи автоматически вытесняются при переполнении. - */ + public class LogBuffer { private final int capacity; private final LogEntry[] buffer; @@ -22,9 +19,7 @@ public LogBuffer(int capacity) { this.buffer = new LogEntry[capacity]; } - /** - * Добавляет запись. При переполнении самая старая запись перезаписывается. - */ + public void add(LogEntry entry) { lock.writeLock().lock(); try { @@ -40,9 +35,7 @@ public void add(LogEntry entry) { } } - /** - * Возвращает текущее количество записей. - */ + public int size() { lock.readLock().lock(); try { @@ -52,9 +45,7 @@ public int size() { } } - /** - * Возвращает снимок всех записей в порядке от старых к новым. - */ + public List getAll() { lock.readLock().lock(); try { @@ -68,10 +59,7 @@ public List getAll() { } } - /** - * Возвращает снимок подсписка записей от startIndex (включительно) до endIndex (исключительно). - * Индексы соответствуют позициям в порядке от старых к новым. - */ + public List getRange(int startIndex, int endIndex) { if (startIndex < 0 || endIndex > size || startIndex > endIndex) { throw new IndexOutOfBoundsException("Invalid range: " + startIndex + ".." + endIndex); diff --git a/robots/src/log/LogWindowSource.java b/robots/src/log/LogWindowSource.java index b31f732..c91a1de 100644 --- a/robots/src/log/LogWindowSource.java +++ b/robots/src/log/LogWindowSource.java @@ -2,15 +2,7 @@ import java.util.ArrayList; import java.util.Collections; -/** - * Что починить: - * 1. Этот класс порождает утечку ресурсов (связанные слушатели оказываются - * удерживаемыми в памяти) - * 2. Этот класс хранит активные сообщения лога, но в такой реализации он - * их лишь накапливает. Надо же, чтобы количество сообщений в логе было ограничено - * величиной m_iQueueLength (т.е. реально нужна очередь сообщений - * ограниченного размера) - */ + public class LogWindowSource { private final LogBuffer logBuffer; // ограниченная очередь private final ArrayList m_listeners; @@ -57,12 +49,7 @@ public int size() { return logBuffer.size(); } - /** - * Возвращает диапазон записей от startFrom (включительно) в количестве count. - * @param startFrom индекс первой записи (0 – самая старая) - * @param count количество записей - * @return неизменяемый список записей (может быть меньше count, если достигнут конец) - */ + public Iterable range(int startFrom, int count) { int total = size(); if (startFrom < 0 || startFrom >= total) { @@ -72,9 +59,7 @@ public Iterable range(int startFrom, int count) { return logBuffer.getRange(startFrom, endIndex); } - /** - * Возвращает все записи в виде снимка. - */ + public Iterable all() { return logBuffer.getAll(); } From e2d14df991a68dc200725f07562fc05f1d14fae3 Mon Sep 17 00:00:00 2001 From: Alla509 Date: Thu, 21 May 2026 21:33:12 +0500 Subject: [PATCH 12/14] new package collections, junit tests for collection --- .../src/collections/BoundedCircularList.java | 227 ++++++++++++++++++ .../collections/BoundedCircularListTest.java | 71 ++++++ robots/src/log/LogBuffer.java | 67 +----- robots/src/log/LogWindowSource.java | 63 ++--- robots/src/log/Logger.java | 2 +- 5 files changed, 340 insertions(+), 90 deletions(-) create mode 100644 robots/src/collections/BoundedCircularList.java create mode 100644 robots/src/collections/BoundedCircularListTest.java diff --git a/robots/src/collections/BoundedCircularList.java b/robots/src/collections/BoundedCircularList.java new file mode 100644 index 0000000..40a8535 --- /dev/null +++ b/robots/src/collections/BoundedCircularList.java @@ -0,0 +1,227 @@ +package collections; + +import java.util.*; +import java.util.concurrent.locks.ReentrantReadWriteLock; + + +public class BoundedCircularList implements List { + private final int capacity; + private final Object[] buffer; + private int head; + private int size; + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + public BoundedCircularList(int capacity) { + if (capacity <= 0) throw new IllegalArgumentException("Capacity must be positive"); + this.capacity = capacity; + this.buffer = new Object[capacity]; + } + + + @Override + public int size() { + lock.readLock().lock(); + try { + return size; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + lock.readLock().lock(); + try { + for (int i = 0; i < size; i++) { + if (Objects.equals(buffer[(head + i) % capacity], o)) return true; + } + return false; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public Iterator iterator() { + return snapshot().iterator(); + } + + @Override + public Object[] toArray() { + return snapshot().toArray(); + } + + @Override + public T1[] toArray(T1[] a) { + return snapshot().toArray(a); + } + + @Override + public boolean add(T t) { + lock.writeLock().lock(); + try { + if (size < capacity) { + buffer[(head + size) % capacity] = t; + size++; + } else { + buffer[head] = t; + head = (head + 1) % capacity; + } + return true; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException("Remove not supported"); + } + + @Override + public boolean containsAll(Collection c) { + lock.readLock().lock(); + try { + for (Object o : c) { + if (!contains(o)) return false; + } + return true; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public boolean addAll(Collection c) { + lock.writeLock().lock(); + try { + for (T t : c) add(t); + return !c.isEmpty(); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException("addAll at index not supported"); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException("removeAll not supported"); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException("retainAll not supported"); + } + + @Override + public void clear() { + lock.writeLock().lock(); + try { + Arrays.fill(buffer, null); + head = 0; + size = 0; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public T get(int index) { + if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); + lock.readLock().lock(); + try { + return (T) buffer[(head + index) % capacity]; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public T set(int index, T element) { + throw new UnsupportedOperationException("set not supported"); + } + + @Override + public void add(int index, T element) { + throw new UnsupportedOperationException("add at index not supported"); + } + + @Override + public T remove(int index) { + throw new UnsupportedOperationException("remove by index not supported"); + } + + @Override + public int indexOf(Object o) { + lock.readLock().lock(); + try { + for (int i = 0; i < size; i++) { + if (Objects.equals(buffer[(head + i) % capacity], o)) return i; + } + return -1; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public int lastIndexOf(Object o) { + lock.readLock().lock(); + try { + for (int i = size - 1; i >= 0; i--) { + if (Objects.equals(buffer[(head + i) % capacity], o)) return i; + } + return -1; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public ListIterator listIterator() { + return snapshot().listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return snapshot().listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + lock.readLock().lock(); + try { + if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) throw new IndexOutOfBoundsException(); + List result = new ArrayList<>(toIndex - fromIndex); + for (int i = fromIndex; i < toIndex; i++) { + result.add((T) buffer[(head + i) % capacity]); + } + return Collections.unmodifiableList(result); + } finally { + lock.readLock().unlock(); + } + } + + + public List snapshot() { + lock.readLock().lock(); + try { + List snapshot = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + snapshot.add((T) buffer[(head + i) % capacity]); + } + return snapshot; + } finally { + lock.readLock().unlock(); + } + } +} diff --git a/robots/src/collections/BoundedCircularListTest.java b/robots/src/collections/BoundedCircularListTest.java new file mode 100644 index 0000000..5f39dff --- /dev/null +++ b/robots/src/collections/BoundedCircularListTest.java @@ -0,0 +1,71 @@ +package collections; + +import org.junit.jupiter.api.Test; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + +class BoundedCircularListTest { + + @Test + void testAddAndGet() { + BoundedCircularList list = new BoundedCircularList<>(3); + list.add("A"); + list.add("B"); + list.add("C"); + assertEquals(3, list.size()); + assertEquals("A", list.get(0)); + assertEquals("B", list.get(1)); + assertEquals("C", list.get(2)); + } + + @Test + void testOverflow() { + BoundedCircularList list = new BoundedCircularList<>(3); + list.add("A"); + list.add("B"); + list.add("C"); + list.add("D"); // перезаписывает A + assertEquals(3, list.size()); + assertEquals("B", list.get(0)); + assertEquals("C", list.get(1)); + assertEquals("D", list.get(2)); + } + + @Test + void testSubList() { + BoundedCircularList list = new BoundedCircularList<>(5); + for (int i = 0; i < 5; i++) list.add("E" + i); + List sub = list.subList(1, 4); + assertEquals(List.of("E1", "E2", "E3"), sub); + } + + @Test + void testSnapshotIterator() { + BoundedCircularList list = new BoundedCircularList<>(3); + list.add("X"); + list.add("Y"); + var it = list.iterator(); + list.add("Z"); // добавление после создания итератора не мешает + assertTrue(it.hasNext()); + assertEquals("X", it.next()); + assertEquals("Y", it.next()); + // итератор имеет снимок, поэтому Z не виден + assertFalse(it.hasNext()); + } + + @Test + void testThreadSafety() throws InterruptedException { + BoundedCircularList list = new BoundedCircularList<>(100); + Thread t1 = new Thread(() -> { + for (int i = 0; i < 100; i++) list.add(i); + }); + Thread t2 = new Thread(() -> { + for (int i = 0; i < 100; i++) list.size(); + }); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + assertEquals(100, list.size()); + } +} diff --git a/robots/src/log/LogBuffer.java b/robots/src/log/LogBuffer.java index e945bbc..9757ba8 100644 --- a/robots/src/log/LogBuffer.java +++ b/robots/src/log/LogBuffer.java @@ -1,78 +1,29 @@ package log; -import java.util.ArrayList; -import java.util.Collections; +import collections.BoundedCircularList; import java.util.List; -import java.util.concurrent.locks.ReentrantReadWriteLock; - public class LogBuffer { - private final int capacity; - private final LogEntry[] buffer; - private int head; // индекс самой старой записи - private int size; // текущее количество записей - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final BoundedCircularList buffer; public LogBuffer(int capacity) { - if (capacity <= 0) throw new IllegalArgumentException("Capacity must be positive"); - this.capacity = capacity; - this.buffer = new LogEntry[capacity]; + buffer = new BoundedCircularList<>(capacity); } - public void add(LogEntry entry) { - lock.writeLock().lock(); - try { - if (size < capacity) { - buffer[(head + size) % capacity] = entry; - size++; - } else { - buffer[head] = entry; - head = (head + 1) % capacity; - } - } finally { - lock.writeLock().unlock(); - } + buffer.add(entry); } - public int size() { - lock.readLock().lock(); - try { - return size; - } finally { - lock.readLock().unlock(); - } + return buffer.size(); } - public List getAll() { - lock.readLock().lock(); - try { - List snapshot = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - snapshot.add(buffer[(head + i) % capacity]); - } - return Collections.unmodifiableList(snapshot); - } finally { - lock.readLock().unlock(); - } + return buffer.snapshot(); } - - public List getRange(int startIndex, int endIndex) { - if (startIndex < 0 || endIndex > size || startIndex > endIndex) { - throw new IndexOutOfBoundsException("Invalid range: " + startIndex + ".." + endIndex); - } - lock.readLock().lock(); - try { - List range = new ArrayList<>(endIndex - startIndex); - for (int i = startIndex; i < endIndex; i++) { - range.add(buffer[(head + i) % capacity]); - } - return Collections.unmodifiableList(range); - } finally { - lock.readLock().unlock(); - } + // Для совместимости + public List getRange(int start, int end) { + return buffer.subList(start, end); } } diff --git a/robots/src/log/LogWindowSource.java b/robots/src/log/LogWindowSource.java index c91a1de..952231a 100644 --- a/robots/src/log/LogWindowSource.java +++ b/robots/src/log/LogWindowSource.java @@ -1,66 +1,67 @@ package log; +import collections.BoundedCircularList; import java.util.ArrayList; import java.util.Collections; +import java.util.List; public class LogWindowSource { - private final LogBuffer logBuffer; // ограниченная очередь - private final ArrayList m_listeners; - private volatile LogChangeListener[] m_activeListeners; + private final BoundedCircularList buffer; + private final List listeners = new ArrayList<>(); + private volatile LogChangeListener[] activeListeners; - public LogWindowSource(int iQueueLength) { - logBuffer = new LogBuffer(iQueueLength); - m_listeners = new ArrayList<>(); + public LogWindowSource(int capacity) { + this.buffer = new BoundedCircularList<>(capacity); } public void registerListener(LogChangeListener listener) { - synchronized (m_listeners) { - m_listeners.add(listener); - m_activeListeners = null; + synchronized (listeners) { + listeners.add(listener); + activeListeners = null; } } public void unregisterListener(LogChangeListener listener) { - synchronized (m_listeners) { - m_listeners.remove(listener); - m_activeListeners = null; + synchronized (listeners) { + listeners.remove(listener); + activeListeners = null; } } - public void append(LogLevel logLevel, String strMessage) { - LogEntry entry = new LogEntry(logLevel, strMessage); - logBuffer.add(entry); - LogChangeListener[] activeListeners = m_activeListeners; - if (activeListeners == null) { - synchronized (m_listeners) { - if (m_activeListeners == null) { - activeListeners = m_listeners.toArray(new LogChangeListener[0]); - m_activeListeners = activeListeners; + public void append(LogLevel level, String message) { + LogEntry entry = new LogEntry(level, message); + buffer.add(entry); + + LogChangeListener[] copy = activeListeners; + if (copy == null) { + synchronized (listeners) { + if (activeListeners == null) { + activeListeners = listeners.toArray(new LogChangeListener[0]); + copy = activeListeners; } } } - for (LogChangeListener listener : activeListeners) { + for (LogChangeListener listener : copy) { listener.onLogChanged(); } } public int size() { - return logBuffer.size(); + return buffer.size(); } + public Iterable all() { + return buffer.subList(0, buffer.size()); + } + public Iterable range(int startFrom, int count) { - int total = size(); + int total = buffer.size(); if (startFrom < 0 || startFrom >= total) { return Collections.emptyList(); } - int endIndex = Math.min(startFrom + count, total); - return logBuffer.getRange(startFrom, endIndex); - } - - - public Iterable all() { - return logBuffer.getAll(); + int end = Math.min(startFrom + count, total); + return buffer.subList(startFrom, end); } } diff --git a/robots/src/log/Logger.java b/robots/src/log/Logger.java index b008a5d..91c71b6 100644 --- a/robots/src/log/Logger.java +++ b/robots/src/log/Logger.java @@ -4,7 +4,7 @@ public final class Logger { private static final LogWindowSource defaultLogSource; static { - defaultLogSource = new LogWindowSource(100); + defaultLogSource = new LogWindowSource(5); } private Logger() From 7c6b1e7b2278230d3afbed9e867624a5f5d52893 Mon Sep 17 00:00:00 2001 From: Alla509 Date: Fri, 22 May 2026 18:29:30 +0500 Subject: [PATCH 13/14] fixed bug with class LogWindow --- robots/src/gui/LogWindow.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/robots/src/gui/LogWindow.java b/robots/src/gui/LogWindow.java index 723d3e2..fd1440d 100644 --- a/robots/src/gui/LogWindow.java +++ b/robots/src/gui/LogWindow.java @@ -16,14 +16,14 @@ public class LogWindow extends JInternalFrame implements LogChangeListener private LogWindowSource m_logSource; private TextArea m_logContent; - public LogWindow(LogWindowSource logSource) + public LogWindow(LogWindowSource logSource) { super("Протокол работы", true, true, true, true); m_logSource = logSource; m_logSource.registerListener(this); m_logContent = new TextArea(""); m_logContent.setSize(200, 500); - + JPanel panel = new JPanel(new BorderLayout()); panel.add(m_logContent, BorderLayout.CENTER); getContentPane().add(panel); @@ -41,7 +41,7 @@ private void updateLogContent() m_logContent.setText(content.toString()); m_logContent.invalidate(); } - + @Override public void onLogChanged() { From e2e80a2353cdb6586dc2c4e8b8e14fce8f1ec5a7 Mon Sep 17 00:00:00 2001 From: Alla509 Date: Thu, 28 May 2026 21:31:54 +0500 Subject: [PATCH 14/14] in BoundedCircularQueue extend AbstractQueue instead of List --- .../src/collections/BoundedCircularList.java | 227 ------------------ .../collections/BoundedCircularListTest.java | 71 ------ .../src/collections/BoundedCircularQueue.java | 103 ++++++++ .../collections/BoundedCircularQueueTest.java | 64 +++++ robots/src/log/LogBuffer.java | 13 +- robots/src/log/LogWindowSource.java | 14 +- 6 files changed, 179 insertions(+), 313 deletions(-) delete mode 100644 robots/src/collections/BoundedCircularList.java delete mode 100644 robots/src/collections/BoundedCircularListTest.java create mode 100644 robots/src/collections/BoundedCircularQueue.java create mode 100644 robots/src/collections/BoundedCircularQueueTest.java diff --git a/robots/src/collections/BoundedCircularList.java b/robots/src/collections/BoundedCircularList.java deleted file mode 100644 index 40a8535..0000000 --- a/robots/src/collections/BoundedCircularList.java +++ /dev/null @@ -1,227 +0,0 @@ -package collections; - -import java.util.*; -import java.util.concurrent.locks.ReentrantReadWriteLock; - - -public class BoundedCircularList implements List { - private final int capacity; - private final Object[] buffer; - private int head; - private int size; - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - - public BoundedCircularList(int capacity) { - if (capacity <= 0) throw new IllegalArgumentException("Capacity must be positive"); - this.capacity = capacity; - this.buffer = new Object[capacity]; - } - - - @Override - public int size() { - lock.readLock().lock(); - try { - return size; - } finally { - lock.readLock().unlock(); - } - } - - @Override - public boolean isEmpty() { - return size() == 0; - } - - @Override - public boolean contains(Object o) { - lock.readLock().lock(); - try { - for (int i = 0; i < size; i++) { - if (Objects.equals(buffer[(head + i) % capacity], o)) return true; - } - return false; - } finally { - lock.readLock().unlock(); - } - } - - @Override - public Iterator iterator() { - return snapshot().iterator(); - } - - @Override - public Object[] toArray() { - return snapshot().toArray(); - } - - @Override - public T1[] toArray(T1[] a) { - return snapshot().toArray(a); - } - - @Override - public boolean add(T t) { - lock.writeLock().lock(); - try { - if (size < capacity) { - buffer[(head + size) % capacity] = t; - size++; - } else { - buffer[head] = t; - head = (head + 1) % capacity; - } - return true; - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public boolean remove(Object o) { - throw new UnsupportedOperationException("Remove not supported"); - } - - @Override - public boolean containsAll(Collection c) { - lock.readLock().lock(); - try { - for (Object o : c) { - if (!contains(o)) return false; - } - return true; - } finally { - lock.readLock().unlock(); - } - } - - @Override - public boolean addAll(Collection c) { - lock.writeLock().lock(); - try { - for (T t : c) add(t); - return !c.isEmpty(); - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public boolean addAll(int index, Collection c) { - throw new UnsupportedOperationException("addAll at index not supported"); - } - - @Override - public boolean removeAll(Collection c) { - throw new UnsupportedOperationException("removeAll not supported"); - } - - @Override - public boolean retainAll(Collection c) { - throw new UnsupportedOperationException("retainAll not supported"); - } - - @Override - public void clear() { - lock.writeLock().lock(); - try { - Arrays.fill(buffer, null); - head = 0; - size = 0; - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public T get(int index) { - if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); - lock.readLock().lock(); - try { - return (T) buffer[(head + index) % capacity]; - } finally { - lock.readLock().unlock(); - } - } - - @Override - public T set(int index, T element) { - throw new UnsupportedOperationException("set not supported"); - } - - @Override - public void add(int index, T element) { - throw new UnsupportedOperationException("add at index not supported"); - } - - @Override - public T remove(int index) { - throw new UnsupportedOperationException("remove by index not supported"); - } - - @Override - public int indexOf(Object o) { - lock.readLock().lock(); - try { - for (int i = 0; i < size; i++) { - if (Objects.equals(buffer[(head + i) % capacity], o)) return i; - } - return -1; - } finally { - lock.readLock().unlock(); - } - } - - @Override - public int lastIndexOf(Object o) { - lock.readLock().lock(); - try { - for (int i = size - 1; i >= 0; i--) { - if (Objects.equals(buffer[(head + i) % capacity], o)) return i; - } - return -1; - } finally { - lock.readLock().unlock(); - } - } - - @Override - public ListIterator listIterator() { - return snapshot().listIterator(); - } - - @Override - public ListIterator listIterator(int index) { - return snapshot().listIterator(index); - } - - @Override - public List subList(int fromIndex, int toIndex) { - lock.readLock().lock(); - try { - if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) throw new IndexOutOfBoundsException(); - List result = new ArrayList<>(toIndex - fromIndex); - for (int i = fromIndex; i < toIndex; i++) { - result.add((T) buffer[(head + i) % capacity]); - } - return Collections.unmodifiableList(result); - } finally { - lock.readLock().unlock(); - } - } - - - public List snapshot() { - lock.readLock().lock(); - try { - List snapshot = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - snapshot.add((T) buffer[(head + i) % capacity]); - } - return snapshot; - } finally { - lock.readLock().unlock(); - } - } -} diff --git a/robots/src/collections/BoundedCircularListTest.java b/robots/src/collections/BoundedCircularListTest.java deleted file mode 100644 index 5f39dff..0000000 --- a/robots/src/collections/BoundedCircularListTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package collections; - -import org.junit.jupiter.api.Test; -import java.util.List; -import static org.junit.jupiter.api.Assertions.*; - -class BoundedCircularListTest { - - @Test - void testAddAndGet() { - BoundedCircularList list = new BoundedCircularList<>(3); - list.add("A"); - list.add("B"); - list.add("C"); - assertEquals(3, list.size()); - assertEquals("A", list.get(0)); - assertEquals("B", list.get(1)); - assertEquals("C", list.get(2)); - } - - @Test - void testOverflow() { - BoundedCircularList list = new BoundedCircularList<>(3); - list.add("A"); - list.add("B"); - list.add("C"); - list.add("D"); // перезаписывает A - assertEquals(3, list.size()); - assertEquals("B", list.get(0)); - assertEquals("C", list.get(1)); - assertEquals("D", list.get(2)); - } - - @Test - void testSubList() { - BoundedCircularList list = new BoundedCircularList<>(5); - for (int i = 0; i < 5; i++) list.add("E" + i); - List sub = list.subList(1, 4); - assertEquals(List.of("E1", "E2", "E3"), sub); - } - - @Test - void testSnapshotIterator() { - BoundedCircularList list = new BoundedCircularList<>(3); - list.add("X"); - list.add("Y"); - var it = list.iterator(); - list.add("Z"); // добавление после создания итератора не мешает - assertTrue(it.hasNext()); - assertEquals("X", it.next()); - assertEquals("Y", it.next()); - // итератор имеет снимок, поэтому Z не виден - assertFalse(it.hasNext()); - } - - @Test - void testThreadSafety() throws InterruptedException { - BoundedCircularList list = new BoundedCircularList<>(100); - Thread t1 = new Thread(() -> { - for (int i = 0; i < 100; i++) list.add(i); - }); - Thread t2 = new Thread(() -> { - for (int i = 0; i < 100; i++) list.size(); - }); - t1.start(); - t2.start(); - t1.join(); - t2.join(); - assertEquals(100, list.size()); - } -} diff --git a/robots/src/collections/BoundedCircularQueue.java b/robots/src/collections/BoundedCircularQueue.java new file mode 100644 index 0000000..3981cde --- /dev/null +++ b/robots/src/collections/BoundedCircularQueue.java @@ -0,0 +1,103 @@ +package collections; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class BoundedCircularQueue extends AbstractQueue { + private final int capacity; + private final Object[] buffer; + private final AtomicInteger head = new AtomicInteger(0); + private final AtomicInteger size = new AtomicInteger(0); + + public BoundedCircularQueue(int capacity) { + if (capacity <= 0) throw new IllegalArgumentException("Capacity must be positive"); + this.capacity = capacity; + this.buffer = new Object[capacity]; + } + + @Override + public boolean offer(E e) { + if (e == null) throw new NullPointerException(); + while (true) { + int currentSize = size.get(); + if (currentSize < capacity) { + if (size.compareAndSet(currentSize, currentSize + 1)) { + int pos = (head.get() + currentSize) % capacity; + buffer[pos] = e; + return true; + } + } else { + int oldHead = head.get(); + int newHead = (oldHead + 1) % capacity; + if (head.compareAndSet(oldHead, newHead)) { + buffer[oldHead] = e; + return true; + } + } + } + } + + @Override + public E poll() { + while (true) { + int currentSize = size.get(); + if (currentSize == 0) return null; + if (size.compareAndSet(currentSize, currentSize - 1)) { + int idx = head.getAndUpdate(h -> (h + 1) % capacity); + @SuppressWarnings("unchecked") + E result = (E) buffer[idx]; + buffer[idx] = null; + return result; + } + } + } + + @Override + public E peek() { + int currentSize = size.get(); + if (currentSize == 0) return null; + @SuppressWarnings("unchecked") + E result = (E) buffer[head.get()]; + return result; + } + + @Override + public int size() { + return size.get(); + } + + @Override + public Iterator iterator() { + return snapshot().iterator(); + } + + public List getAll() { + return snapshot(); + } + + public List getRange(int start, int end) { + int currentSize = size.get(); + if (start < 0 || end > currentSize || start > end) + throw new IndexOutOfBoundsException("Invalid range: " + start + ".." + end); + int currentHead = head.get(); + List result = new ArrayList<>(end - start); + for (int i = start; i < end; i++) { + @SuppressWarnings("unchecked") + E element = (E) buffer[(currentHead + i) % capacity]; + result.add(element); + } + return Collections.unmodifiableList(result); + } + + private List snapshot() { + int currentHead = head.get(); + int currentSize = size.get(); + List result = new ArrayList<>(currentSize); + for (int i = 0; i < currentSize; i++) { + @SuppressWarnings("unchecked") + E element = (E) buffer[(currentHead + i) % capacity]; + result.add(element); + } + return Collections.unmodifiableList(result); + } +} diff --git a/robots/src/collections/BoundedCircularQueueTest.java b/robots/src/collections/BoundedCircularQueueTest.java new file mode 100644 index 0000000..0cb5b6f --- /dev/null +++ b/robots/src/collections/BoundedCircularQueueTest.java @@ -0,0 +1,64 @@ +package collections; + +import org.junit.jupiter.api.Test; +import java.util.*; +import static org.junit.jupiter.api.Assertions.*; + +class BoundedCircularQueueTest { + + @Test + void testOfferAndPoll() { + BoundedCircularQueue q = new BoundedCircularQueue<>(3); + assertTrue(q.offer("A")); + assertTrue(q.offer("B")); + assertTrue(q.offer("C")); + assertEquals(3, q.size()); + assertEquals("A", q.poll()); + assertEquals("B", q.peek()); + assertEquals(2, q.size()); + } + + @Test + void testOverflow() { + BoundedCircularQueue q = new BoundedCircularQueue<>(3); + q.offer("A"); + q.offer("B"); + q.offer("C"); + q.offer("D"); // перезаписывает A + assertEquals(3, q.size()); + assertEquals("B", q.poll()); + assertEquals("C", q.poll()); + assertEquals("D", q.poll()); + assertNull(q.poll()); + } + + @Test + void testGetAll() { + BoundedCircularQueue q = new BoundedCircularQueue<>(5); + for (int i = 0; i < 5; i++) q.offer(i); + assertEquals(List.of(0, 1, 2, 3, 4), q.getAll()); + } + + @Test + void testGetRange() { + BoundedCircularQueue q = new BoundedCircularQueue<>(5); + q.offer("a"); + q.offer("b"); + q.offer("c"); + q.offer("d"); + assertEquals(List.of("b", "c"), q.getRange(1, 3)); + } + + @Test + void testIteratorSnapshot() { + BoundedCircularQueue q = new BoundedCircularQueue<>(3); + q.offer("X"); + q.offer("Y"); + Iterator it = q.iterator(); + q.offer("Z"); // добавление не ломает итератор + assertTrue(it.hasNext()); + assertEquals("X", it.next()); + assertEquals("Y", it.next()); + assertFalse(it.hasNext()); + } +} diff --git a/robots/src/log/LogBuffer.java b/robots/src/log/LogBuffer.java index 9757ba8..2b2c824 100644 --- a/robots/src/log/LogBuffer.java +++ b/robots/src/log/LogBuffer.java @@ -1,17 +1,17 @@ package log; -import collections.BoundedCircularList; +import collections.BoundedCircularQueue; import java.util.List; public class LogBuffer { - private final BoundedCircularList buffer; + private final BoundedCircularQueue buffer; public LogBuffer(int capacity) { - buffer = new BoundedCircularList<>(capacity); + buffer = new BoundedCircularQueue<>(capacity); } public void add(LogEntry entry) { - buffer.add(entry); + buffer.offer(entry); } public int size() { @@ -19,11 +19,10 @@ public int size() { } public List getAll() { - return buffer.snapshot(); + return buffer.getAll(); } - // Для совместимости public List getRange(int start, int end) { - return buffer.subList(start, end); + return buffer.getRange(start, end); } } diff --git a/robots/src/log/LogWindowSource.java b/robots/src/log/LogWindowSource.java index 952231a..54d3036 100644 --- a/robots/src/log/LogWindowSource.java +++ b/robots/src/log/LogWindowSource.java @@ -1,17 +1,17 @@ package log; -import collections.BoundedCircularList; +import collections.BoundedCircularQueue; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class LogWindowSource { - private final BoundedCircularList buffer; + private final BoundedCircularQueue buffer; private final List listeners = new ArrayList<>(); private volatile LogChangeListener[] activeListeners; public LogWindowSource(int capacity) { - this.buffer = new BoundedCircularList<>(capacity); + this.buffer = new BoundedCircularQueue<>(capacity); } public void registerListener(LogChangeListener listener) { @@ -53,15 +53,13 @@ public int size() { public Iterable all() { - return buffer.subList(0, buffer.size()); + return buffer.getAll(); } public Iterable range(int startFrom, int count) { int total = buffer.size(); - if (startFrom < 0 || startFrom >= total) { - return Collections.emptyList(); - } + if (startFrom < 0 || startFrom >= total) return Collections.emptyList(); int end = Math.min(startFrom + count, total); - return buffer.subList(startFrom, end); + return buffer.getRange(startFrom, end); } }