From a093762658f43afaf219991f346ff7050b087a21 Mon Sep 17 00:00:00 2001 From: seventhback777 <903157209@qq.com> Date: Wed, 24 Sep 2025 17:22:29 +1000 Subject: [PATCH 1/7] key information --- .scripts/README.md | 13 +++++ include/AboutScreen.h | 3 +- include/about.md | 123 ++++++++++++++++++++++++++++++++++++++++++ resources/about.md | 1 + scripts.tar.gz | Bin 0 -> 4701 bytes src/about.md | 1 + stats/about.md | 1 + 7 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 .scripts/README.md create mode 100644 include/about.md create mode 100644 resources/about.md create mode 100644 scripts.tar.gz create mode 100644 src/about.md create mode 100644 stats/about.md diff --git a/.scripts/README.md b/.scripts/README.md new file mode 100644 index 0000000..eda4296 --- /dev/null +++ b/.scripts/README.md @@ -0,0 +1,13 @@ +About generate-stats.sh +This is an automated script that can automatically accomplish three things +1. Counts lines in .cpp and .h files. + +2. Counts the total number of commits and remote branches. + +3. Counts contributors and generates a contributor list plus a headcount. + +All of these are written to stats/ + +About test-menu.cpp + +this is a small testing tool in the development stage. It enables testers to pull a single game repository from GitHub using the local terminal and compile it into a startup script according to the configuration command in a fixed path, making it one-click executable. At the same time, it supports viewing and modifying the config.cfg of the game \ No newline at end of file diff --git a/include/AboutScreen.h b/include/AboutScreen.h index 806c79d..979dfc9 100644 --- a/include/AboutScreen.h +++ b/include/AboutScreen.h @@ -45,4 +45,5 @@ class AboutScreen { void main(); }; -#endif \ No newline at end of file +#endif + diff --git a/include/about.md b/include/about.md new file mode 100644 index 0000000..3438fcb --- /dev/null +++ b/include/about.md @@ -0,0 +1,123 @@ +1.AboutScreen.h +The developer contribution list page will scroll through the contribution information of developers and obtain information from the data collected by the previous statistics script + +2.ArcadeMachine.h +The master control type of the entire "arcade front-end program" - organizing the startup animation, main menu, game list, option menu, button clicks, sounds and layout all together + +3.Audio.h +The music player plays the main menu music by default upon startup. It can switch songs and adjust the volume, ensuring that games/menus have a unified music management interface + +4.Button.h +The parent class of all buttons provides the basic attributes and behaviors for the buttons + +5.ButtonNode.h +Button nodes are used to form a bidirectional circular linked list of a bunch of buttons. This enables the menu cursor to move in a loop between buttons, just like in a real arcade machine. You can push in one direction without going all the way, making there no sense of boundaries. + +6.cell.h +The grid cell, as the smallest drawing unit in the machine, supports storing four types: EMPTY,BMP,SPT(Animation Sprite), and BTN. It also supports functions such as center alignment, how large this cell should occupy (merging other base cells), etc. A very important drawing unit + +7.ConfigData.h +A very important header file is the "Data model manual" for each game. It is through this that the menu system knows which games are available and how to start them. Including: All metadata of each game + +Basic information: m_id + + m_repo + + m_language + + m_folder + +Display information: m_image + + m_title + + m_genre + + m_rating + + m_author + + m_description + +Executable file path + + The executable path of Win + + The executable path of Linux + + The executable path of MacOS + +It can open the config.txt of each game, read this information from it (here we have to mention config.txt, which is the information that every game uploader must provide), and then convert this information from text information into object attributes for the convenience of using it for config later. + +8.Configuration.h +Global configuration constants, uniformly managing environment-related parameters such as "resolution, path separator, CPU architecture, operating system type, and executable file extension" + +9.Database.h +The database encapsulation for arcade menu/statistics provides common operation methods such as table creation, addition, deletion, modification, query, and printing, making it convenient for direct invocation elsewhere + +10.GameData.h +To record each round encapsulation, wrote in the database a is fixed: gameData, it exists fields: gamaName, startTime, endTime, rating, highScore. It will even provide aggregated statistics: average score, total duration, highest score, etc. + +11.GameScreenButton.h +The specific implementation class on the game selection menu completes the implementation of button images, drawing, click behavior, etc. In the menu interface, each game/option will be displayed with a GameScreenButton. + +12.GridLayout.h +This actually forms a linkage with the previous grid header file. The two work together to achieve a very good drawing effect. It can divide the entire screen into x rows and y columns. Each cell is a grid, managing the drawing content of each cell, drawing uniformly, automatically calculating scaling, position, etc. based on the cell size, and finally drawing all of them. And it provides the functions of cleaning the screen and releasing memory. + +13.Helper.h +This is a very important toolbox, involving the reading of game directories, etc. + +(1) There is a function called string getFoldName(string entryPath), which uses the path of the passed game file (usually the config.txt of a certain game) to take the directory where it is located as the "game folder name" Facilitate the subsequent reading of resources by ConfigData. + +(2) vectorgetConfigFiles(string dir) recursively traverses the folder, collects all config.txt files, and returns a list of paths. + +(3) vector ConfigDataList() +Call getConfigFiles(string dir), specify the path for it ("./games/games"), scan it once, then read each config.txt in, convert it into a ConfigData object, and fill it in: + folder + id + The fields parsed from the configuration file +Regarding the specified path ("./games/games"), it is necessary to mention the special directory of the game repository. For the storage of games, a special format must be met. It must be written as config.txt and then uniformly placed in the "games" folder. Only in this way can vector ConfigDataList() correctly find the game and load the relevant configuration. That's why it is hard-coded here. + +(4)void resetScreen(GridLayout grid) +A demonstration tool for drawing grids for testing + +(5) void gridLayoutExample() +Open a 600x800 window without loading appContainer.png. Create a 5x5 grid and conduct several layout experiments. + +14.Menu.h +The desktop of the arcade machine + +15.MenuButton.h +The button class of the menu interface inherits from the previous Button + +16.Option.h +The "Settings/Options Menu" of the arcade machine, the Settings interface + +17.OptionButton.h +A dedicated button for the Settings menu + +18.Process.h +Start an external process (basically a game) on Linux/ Raspberry PI and check if it is still running. +1.spawnProcess(std::string directory,std::string fileName) +Generate a new process +Parameters: The first one is placed in the directory where the running file is located, and the second one is placed in the file name of the running program. +Return value: pid_t(ID of the new process) +2.processRunning(pid_t processId) +Check if the process is alive and return T/F + +Why is this file necessary? Because the arcade machine needs to continuously listen to whether the application is still running, if the program is not running, it needs to exit and return to the main menu + +19.Rating.h +Scoring component + +20.Selector.h +Cursor selector component + +21.Splashscreen.h +The opening animation component plays the logo and provides an opening animation + +22.Table.h +The description of a table in the Database provides a blueprint for the tables in the previous database + +23.Tip.h +The screen prompts the implementation of bubbles/small pop-up Windows, which will automatically disappear after a few seconds \ No newline at end of file diff --git a/resources/about.md b/resources/about.md new file mode 100644 index 0000000..466bd82 --- /dev/null +++ b/resources/about.md @@ -0,0 +1 @@ +The resource file stored when the machine starts running. Provide resources for rendering, playback, etc. It is worth mentioning bundles, which can integrate different versions of resources to meet various needs. Among them, resources.txt is a related integration. \ No newline at end of file diff --git a/scripts.tar.gz b/scripts.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..9685a4ab7e778f69a0a72001b2a0aa300ece4a9e GIT binary patch literal 4701 zcmV-j5~A%NiwFP!000001MNF&ciXtJ`C6aj|A1&Wmb})>acuX>arW4<)>hZA%5t-N zZ7&Z+LK16=WC_xbZqnc08GsKEf@C>O)1G^c4+#YffSJL(0oR!oIzhx5ul{|l32l3G z^Hp=JxxT%*&i`*V*Li=^UafC7-@V&vZfWAhbhemxXsxButo|K01x z!0{RblZ}^`!U?H`L&7Csd1-QMJ0YnB>2Db2Uk%ssD69Fyn&sQH9z8_MOOYR2BXdGo}p2v?o6T--|+L5J!F}s4T9n+g~~YOPW1>7Sp3T zJQMtWBY@iuYmgtlZ#2qz|8>+L&;aeU@A}iB6CK5aPj;Dnn6cG!^*^)!5oJ+rLcJLE z?(5nZH1b>T-p#Q88=K9Iw~GDWcncxHi~auw?K{V_+}Nh%z2mbeq~_$q@>1S8bX>|F zSwxj?4JeCj`e4x@D)dgwpzu5Pz!2H>aiKG!;RLqly~|56bG#8jE6W1Yq9Ew@&hk?J zd&tNxcD7nMb~`u`bODSUpoa4gyM5!bcMPDVaTEot)j&la$Ah}%Pf9x~^xjFw*zcb1 z^?o%@;TxwNboMU~4KZ@rJwBM4)c$w?a6{H=S#cOrFEY?sR8})M2dwM`n3dB*<7Dp_ z1gtU~-d8V+6mo*_|<#*yjSE&w2tJkuqX z@A`2FP6x;#lv!p#3Hk}9px5v8PKKAflz~=Ela=#`-k%IpoKqZoiYmZgW^f z;(!j#*ahPb?h&RiVupZ{c}qPq4E+h|kA1+V59XEZg%&tAa$;IzaAV{wz_`-!J!1P7 z7+^1+knZXEWgq!{aPj-{5}G;D^K<7{XgmY@qI2A7 zBY64mV|HRyGIH+ta7aumiXjwuBxC9Z!`RJ$AnG%N_aL{`L^eJoSnT!Mdj}m5-^Z9NP z*L85nc?E8p!=+q5Pm~t<%E-`%5CMKtu2-J#VLIj6W@r;6n zXvW?}yW2I|M|(X3f}z7s!fmPhq>F;S@wPfWviA#&tiR1 z*eiDY#i92op{eZU(_LzMpz~nY13xswM_{W9p>ht>_$E7oe#+h}x$LQVJ@Fv%oih~G@EPSF@lo&^|& z_z6pdlzL*KGptzT^)6|qyYfBIQ3)qiDe-xwbfeGWzmP_8=y9YzBRzM%V?)3f#eu;c zTolHjt|6@yZ1xHyXX!ox9vcwS2B#e1ATWib=v1%vg81V_qYrMhX5!psNy!(t#6}Zgv!o;cn01*lc3+k&4*T zTd$SCiw@$^At-_9reU3pOBwx{{Ce~uGPu1k?o78LWVR|~mxl}~)o?x{ zgxK9JdeD^>Vv+ZXaga|f6*}P1dhxiZ%jk@Sxs&Bfe^4xvcIldKX$@cwl#4+Wnh*ss zYKVhXWFMRv8mg7RXN$8%IC4NAW&v+l9pkRbYHeLc9>>;ppTQFzOC*baD5^eo)e38U zmaH-!wD>eIf-=zJC}cv!%ghn2=5#w=MkCOV8-Te<))!)ev9aS_HB(71TE5#s7fIMD zC{1A6Xn-;76X70?-H$;XDhWJVwW3cf&?)BE!CE3m0jpFgG6M*@dW5^GYm{KX5sXc51n!KwV4xFe zfhduNIS|Nyww7XUK7AtEAv74Wq;N4!Fj0+MHC;kRj0;e54lvJ>hCa*apMc#Z17Pw! zgj$TOpfls2IZ&#@T12@P&!VDWI9_x8(%KGes@(!uEmFUda9_xLo)|DBj6r5Et~uMX$Ge}o5im^+a?}_U&3}K;f@1YT?EmqP5fc9<$~-h zaA>#4A?(h7gW**&VwHmk8^mmE;Bi*UMp@;UuzKANp1K6FSJAi=Cz#!U)LsxMF(387>7z-i!Pn@E}Kz?T5;R~G^%+JChK0W(| zv++lL1o5;I8uf7OvUi-BDOgRlF9|hbM>&S`Ju0Y)e1#b7t&*9ctcgiUFsrPB5K{vI zY3!0D5=-M+Jba%r@R1DSy9f&`+;dE zK*@ZWfhEMGS)j*>bO0|yHPng~l%U{te@T|PaHP>Nv^H@QiS zBrD2=c_*YPOO5N;KxfhgL@bqU@;cdthdr{P;X8oDrI57`Lnj110S(wk`@8wW3*CC1z~49J#ii)&pdNT-sKZ_GPL>TTI9g#RrqinvRh8V>U7#J z4jymgGR4l%6M`5V5vx==)T-(>xTf3GbtZT&=iE;qU>&a+D{!h)!H(FHTj>Op#RFto zvszea#zY9EVs&O=f5`V%3&aTF=tHuNa=|G2MPho7SnXhh2PZ{fEOQ9_5zEZ(?LmdZ!SBy?%Y|9ry(AxhJB&hb1j8dk_JF zrNGc=;CN>ESSS6lgLe+DO9sHiTOuwF0RdR#3v6AUMC~T=g<|#ctVcC_x-3U$_~dO8 z40HbT*qP2dfI@w#(YWBL5h61|MG^DA=JCwS)J0hqZ|yPP#}Z|_R0z|!QHK)?MGgo4 zI&vmhX2uX7Edk~bg3=#X#X6$8iIr;xhwiZFC;bUycNcGxlw>QU2O#UoS6rS87#Fcb z$O@NYIuTn?_NwZo*}UvO5gk9^*I+!;djlEaIp*P558!oWKM8xDDhY@ij&D#Dn3L~g z4UkKtQt`r8t0|h85AZqqWNmIjmen2kp6`Y7%*w#0EI~f-rTYt(PZct-%&Q0vx+Z;P zuhij6$CGa1bGQp@m8NBEbs9w}j63tJ|lSI-HN5f5*vI7I!@dr&02KfYPzG89g7^vz{@ZtImY-~O^)-FD)&WujKvc?W0N2L zU(vdtGz=*9_`SF5LmFrEQ|DJ_+&e(pS7zM3KOOmhX&#PWs{EWoUsgp3)C141VmO_= zGL8WVe+AMa*I527@tHXz)Ftv{Tmv96UR*-q@`jsSz&x2wh)Ti4o<+69Gl&r$CqTh1 zgEQd5aqow>#Ez-3?#z!v&|RBCwqaAxaww}4NYx1&QwXa>Km}3=#F%!0vXXdRHuL&O z2DQcz%aOH*+0c1J6U70TA#rbAz#_@dCN`#{rR0$)8|TSpI%L^!M3|cv?%bpcH@Q=; zJ@A3+()5b;hOR=e^JE^I<*Mn?iV8)tda*OfS?BVzI0G|SD;F&Ns(BJyu(EtGQvhqC zDHkObY&1XVnn)e~79|<36fDu^oMFJP$sLjTucL`1{GYm#R4@e`GX2ZY;EH(d>9Lo! zZIY6q(X17Sn^6$^0AW558xCxdB(xOO)2tU!E@(5+MDHK_X`)$c3=r+*@qKkUAKO%O2fNXX$p|%6g*?lCS%h(z_g40LHE@7se9Zx zJ*)QH7l7Kw>Pmukk)hO^$xlebkeN@ei*QL`v`AlLSa<{9z@3ueC}V)16n%8`#}hTa$xb2#d2VFfex1Yz1Gc;dWzC zZx>Y@>?bb`gw!rV%84@}?`yUBsk4NbbMvh9SV_NrpM?hBUAv7wQ_4>wKZB<#Y=twD z9#zaM$~?DPB8fyU4l~OIsCKErOrER~&kk6XB*PM+6SllOJ$~%1czIitTzo82`=bz&I!B5S=z|lnFFK^REuLwqPnq>=6>kXs1<}}O@zOx@O8WnZ zmOerAJEU2D2d-Zw3LobP`?s+Ke5dDHgBfCM;dNg|?LndCw$r3yGBfnqh`>Bl^x?klZzN{f2;Hmtf5E2x zvBe`FC(_RrUPe3J7Jo_Bn!l~wr@hbTK^S^l45};gHEqP%BK}xTn=vOCNLh1ajdY3N ziUgVRNtdT9U`15YEEf9~drL6?B0<39sYJ0KZ!^$!78#()g*Bul&#syxjs8#nW$H_N fX)o=iy|kD1(q7t2ducE2Yqx&_;%P+}08jt`YcUUE literal 0 HcmV?d00001 diff --git a/src/about.md b/src/about.md new file mode 100644 index 0000000..fb1e2ab --- /dev/null +++ b/src/about.md @@ -0,0 +1 @@ +Here is the specific implementation file of the previous header file. What they are doing is the same as the purpose of the header file, so the implementation details will not be delved into \ No newline at end of file diff --git a/stats/about.md b/stats/about.md new file mode 100644 index 0000000..1ed5948 --- /dev/null +++ b/stats/about.md @@ -0,0 +1 @@ +Previously, generate-stats.sh generated the target folder of the file, counted the number of cpp lines, contributors, commits, etc \ No newline at end of file From 785345a1f87d5cfb2c30af1fa977c63664fe9e30 Mon Sep 17 00:00:00 2001 From: seventhback777 <903157209@qq.com> Date: Thu, 25 Sep 2025 14:53:45 +1000 Subject: [PATCH 2/7] key information --- .scripts/README.md | 4 ++-- include/about.md | 42 +++++++++++++++++++++--------------------- src/about.md | 2 +- stats/about.md | 2 +- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.scripts/README.md b/.scripts/README.md index eda4296..6655906 100644 --- a/.scripts/README.md +++ b/.scripts/README.md @@ -1,5 +1,5 @@ About generate-stats.sh -This is an automated script that can automatically accomplish three things +This is an automated script that can automatically accomplish three things. 1. Counts lines in .cpp and .h files. 2. Counts the total number of commits and remote branches. @@ -10,4 +10,4 @@ All of these are written to stats/ About test-menu.cpp -this is a small testing tool in the development stage. It enables testers to pull a single game repository from GitHub using the local terminal and compile it into a startup script according to the configuration command in a fixed path, making it one-click executable. At the same time, it supports viewing and modifying the config.cfg of the game \ No newline at end of file +this is a small testing tool in the development stage. It enables testers to pull a single game repository from GitHub using the local terminal and compile it into a startup script according to the configuration command in a fixed path, making it one-click executable. At the same time, it supports viewing and modifying the config.cfg of the game. \ No newline at end of file diff --git a/include/about.md b/include/about.md index 3438fcb..81bdf5e 100644 --- a/include/about.md +++ b/include/about.md @@ -1,23 +1,23 @@ 1.AboutScreen.h -The developer contribution list page will scroll through the contribution information of developers and obtain information from the data collected by the previous statistics script +The developer contribution list page will scroll through the contribution information of developers and obtain information from the data collected by the previous statistics script. 2.ArcadeMachine.h -The master control type of the entire "arcade front-end program" - organizing the startup animation, main menu, game list, option menu, button clicks, sounds and layout all together +The master control type of the entire "arcade front-end program" - organizing the startup animation, main menu, game list, option menu, button clicks, sounds and layout all together. 3.Audio.h -The music player plays the main menu music by default upon startup. It can switch songs and adjust the volume, ensuring that games/menus have a unified music management interface +The music player plays the main menu music by default upon startup. It can switch songs and adjust the volume, ensuring that games/menus have a unified music management interface. 4.Button.h -The parent class of all buttons provides the basic attributes and behaviors for the buttons +The parent class of all buttons provides the basic attributes and behaviors for the buttons. 5.ButtonNode.h Button nodes are used to form a bidirectional circular linked list of a bunch of buttons. This enables the menu cursor to move in a loop between buttons, just like in a real arcade machine. You can push in one direction without going all the way, making there no sense of boundaries. 6.cell.h -The grid cell, as the smallest drawing unit in the machine, supports storing four types: EMPTY,BMP,SPT(Animation Sprite), and BTN. It also supports functions such as center alignment, how large this cell should occupy (merging other base cells), etc. A very important drawing unit +The grid cell, as the smallest drawing unit in the machine, supports storing four types: EMPTY,BMP,SPT(Animation Sprite), and BTN. It also supports functions such as center alignment, how large this cell should occupy (merging other base cells), etc. A very important drawing unit. 7.ConfigData.h -A very important header file is the "Data model manual" for each game. It is through this that the menu system knows which games are available and how to start them. Including: All metadata of each game +A very important header file is the "Data model manual" for each game. It is through this that the menu system knows which games are available and how to start them. Including: All metadata of each game. Basic information: m_id @@ -50,10 +50,10 @@ Executable file path It can open the config.txt of each game, read this information from it (here we have to mention config.txt, which is the information that every game uploader must provide), and then convert this information from text information into object attributes for the convenience of using it for config later. 8.Configuration.h -Global configuration constants, uniformly managing environment-related parameters such as "resolution, path separator, CPU architecture, operating system type, and executable file extension" +Global configuration constants, uniformly managing environment-related parameters such as "resolution, path separator, CPU architecture, operating system type, and executable file extension". 9.Database.h -The database encapsulation for arcade menu/statistics provides common operation methods such as table creation, addition, deletion, modification, query, and printing, making it convenient for direct invocation elsewhere +The database encapsulation for arcade menu/statistics provides common operation methods such as table creation, addition, deletion, modification, query, and printing, making it convenient for direct invocation elsewhere. 10.GameData.h To record each round encapsulation, wrote in the database a is fixed: gameData, it exists fields: gamaName, startTime, endTime, rating, highScore. It will even provide aggregated statistics: average score, total duration, highest score, etc. @@ -79,45 +79,45 @@ Call getConfigFiles(string dir), specify the path for it ("./games/games"), scan Regarding the specified path ("./games/games"), it is necessary to mention the special directory of the game repository. For the storage of games, a special format must be met. It must be written as config.txt and then uniformly placed in the "games" folder. Only in this way can vector ConfigDataList() correctly find the game and load the relevant configuration. That's why it is hard-coded here. (4)void resetScreen(GridLayout grid) -A demonstration tool for drawing grids for testing +A demonstration tool for drawing grids for testing. (5) void gridLayoutExample() Open a 600x800 window without loading appContainer.png. Create a 5x5 grid and conduct several layout experiments. 14.Menu.h -The desktop of the arcade machine +The desktop of the arcade machine. 15.MenuButton.h -The button class of the menu interface inherits from the previous Button +The button class of the menu interface inherits from the previous Button. 16.Option.h -The "Settings/Options Menu" of the arcade machine, the Settings interface +The "Settings/Options Menu" of the arcade machine, the Settings interface. 17.OptionButton.h -A dedicated button for the Settings menu +A dedicated button for the Settings menu. 18.Process.h Start an external process (basically a game) on Linux/ Raspberry PI and check if it is still running. 1.spawnProcess(std::string directory,std::string fileName) Generate a new process Parameters: The first one is placed in the directory where the running file is located, and the second one is placed in the file name of the running program. -Return value: pid_t(ID of the new process) +Return value: pid_t(ID of the new process). 2.processRunning(pid_t processId) -Check if the process is alive and return T/F +Check if the process is alive and return T/F. -Why is this file necessary? Because the arcade machine needs to continuously listen to whether the application is still running, if the program is not running, it needs to exit and return to the main menu +Why is this file necessary? Because the arcade machine needs to continuously listen to whether the application is still running, if the program is not running, it needs to exit and return to the main menu. 19.Rating.h -Scoring component +Scoring component. 20.Selector.h -Cursor selector component +Cursor selector component. 21.Splashscreen.h -The opening animation component plays the logo and provides an opening animation +The opening animation component plays the logo and provides an opening animation. 22.Table.h -The description of a table in the Database provides a blueprint for the tables in the previous database +The description of a table in the Database provides a blueprint for the tables in the previous database. 23.Tip.h -The screen prompts the implementation of bubbles/small pop-up Windows, which will automatically disappear after a few seconds \ No newline at end of file +The screen prompts the implementation of bubbles/small pop-up Windows, which will automatically disappear after a few seconds. \ No newline at end of file diff --git a/src/about.md b/src/about.md index fb1e2ab..59e09f0 100644 --- a/src/about.md +++ b/src/about.md @@ -1 +1 @@ -Here is the specific implementation file of the previous header file. What they are doing is the same as the purpose of the header file, so the implementation details will not be delved into \ No newline at end of file +Here is the specific implementation file of the previous header file. What they are doing is the same as the purpose of the header file, so the implementation details will not be delved into. \ No newline at end of file diff --git a/stats/about.md b/stats/about.md index 1ed5948..bd3be17 100644 --- a/stats/about.md +++ b/stats/about.md @@ -1 +1 @@ -Previously, generate-stats.sh generated the target folder of the file, counted the number of cpp lines, contributors, commits, etc \ No newline at end of file +Previously, generate-stats.sh generated the target folder of the file, counted the number of cpp lines, contributors, commits, etc. \ No newline at end of file From 74cd1a799f00b921daa23a48726402e110bf55f1 Mon Sep 17 00:00:00 2001 From: seventhback777 <903157209@qq.com> Date: Thu, 25 Sep 2025 16:25:54 +1000 Subject: [PATCH 3/7] progress document --- Progress Report/Progress Report.md | 49 +++ ...ArcadeMachine_Pi3_Replication_Checklist.md | 352 ++++++++++++++++++ 2 files changed, 401 insertions(+) create mode 100644 Progress Report/Progress Report.md create mode 100644 about the test machine/ArcadeMachine_Pi3_Replication_Checklist.md diff --git a/Progress Report/Progress Report.md b/Progress Report/Progress Report.md new file mode 100644 index 0000000..a5a81a9 --- /dev/null +++ b/Progress Report/Progress Report.md @@ -0,0 +1,49 @@ +# Arcade Machine — First Progress Report + +**First of all, well done.** + +This should be the first progress report for the arcade machine. Given the project’s risk of discontinuity and the development difficulties that such discontinuity brings, I am writing this document and sincerely recommend that every future development team come back here to write your summary at the end of each semester. + +This semester, we spent a great deal of time accepting the fact of this project’s discontinuity and putting in effort to understand and improve it. Although I believe we did not accomplish much, and I personally feel I did not work hard enough, I still want to provide whatever help I can for future developers. So next I will introduce everything I know and understand, and I hope your development goes smoothly. + +## What is the arcade machine? + +The arcade machine is an entertainment machine developed by Deakin students and placed across Deakin campuses. As of September 25, 2025, the status is: the units currently on campus can only play three games (about one quarter of what’s in the arcade-games Git repository). Various settings and buttons are missing, so the machine functions only as a device for playing games, and the player experience is not ideal. That said, given the pandemic context, the previous developers building this from zero to one was already a remarkable achievement. + +## How is the arcade machine going? + +In fact, the arcade machine consists of two Git repositories—“arcade machine” and “arcade games.” At present, its situation may not be ideal. Based on the arcade machine and arcade games code, we set up two test rigs (one on RPi 5, one on RPi 3B). However, the version running on the real arcade machine differs completely from the version built from the repositories. I do not currently know why there is such a large discrepancy, nor do I know the exact source code base used to build the real machine. Therefore, I cannot tell you with certainty whether the code and logic are the same between the two. + +Based on certain facts (the three games that run on the real arcade are a subset of the arcade-games repository; a previous developer told me that, due to image copyright considerations, they did not use the version from the repo), I can only offer my personal judgment: the code environment on the real machine should be broadly similar to what’s in the repositories, but perhaps due to build issues or other reasons, only three games from arcade-games are present there. + +## What did our team do this semester? + +- Contributed three games to arcade-games: + - a tower defense game (SDL2 engine), + - a 2D RPG (Unity engine), and + - Tetris. +- Wrote explanatory documentation in many places across both the machine and games repositories. +- Prepared two test rigs: one RPi 5 and one RPi 3B. +- Authored this progress report. + +I am ashamed that our team did not do enough, so I wrote this report to provide help until the very last moment. + +## Points to note for the arcade machine + +For now, there are not many special caveats. You can find answers to most questions in the explanatory documents I placed throughout the repositories. The machine repository itself is already relatively complete and does not have any particularly unusual pitfalls. + +One thing worth mentioning is the arcade games side. The repository’s automatic CI will automatically pull the latest updates from games, compile them, and put everything into a compiled games folder. This seems like a great design that would allow automatic updates of games on the arcade machine. However, in the arcade machine’s game-pulling logic, it first looks for the execution path in config, then looks for builds, and if it still cannot find anything, it fails. As a result, the compiled games folder is not actually used in this process. I ultimately did not fully understand the purpose of this design; perhaps it serves as a ready-to-use backup repository of pre-compiled games. + +Another important matter: while developing the machine test rigs, we encountered version incompatibilities with the SplashKit framework. The SplashKit used by developers three years ago is quite different from today’s. Using the latest version leads to compatibility issues. I did not dare to undertake a full repository refactor lightly, so in the test environment I used a SplashKit version prior to 2022.8.21 to fully reproduce the earlier arcade environment. + +## Closing + +I sincerely hope this document helps future developers. When you try to do more for the arcade, getting your hands on the test rigs and the repository documents—together with this report and the notes scattered across the repos—should improve your development experience. I’m sorry I couldn’t do more for you. If you would like to know more details about our development at the time, feel free to email me; I will do my best to help: 903157209hy@gmail.com. + +Wishing you happy times with the arcade machine! + +--- + +September 25, 2025 +Haoyu Liu +Arcade Machine 2025 T3 Development Team diff --git a/about the test machine/ArcadeMachine_Pi3_Replication_Checklist.md b/about the test machine/ArcadeMachine_Pi3_Replication_Checklist.md new file mode 100644 index 0000000..0ecb895 --- /dev/null +++ b/about the test machine/ArcadeMachine_Pi3_Replication_Checklist.md @@ -0,0 +1,352 @@ +# Arcade Machine — Raspberry Pi 3 Replication Checklist (Pi3 Test Machine) + +Below is a checklist of the steps we took to reproduce the current state on a Raspberry Pi, plus unfinished parts and suggestions for improvement. Follow these steps to replicate the environment and get the ArcadeMachine UI running. + +--- + +## Replication Target (Current State) + +On **Raspberry Pi OS** (kernel **6.1**, **g++ 10**), we built only the **SplashKitBackend** static library from the `splashkit-core` branch from early **2022**. + +We used a **shim dynamic library** to package the Backend into the `libSplashKit.so` expected by the legacy project (exporting legacy API names like `open_database` / `run_sql` / `query_success`). + +We also wrote a **header compatibility layer** (`sk-compat`) to map the `splashkit.h` included in the legacy project to the new Backend header/namespace, forwarding commonly used functions and macros so we can compile the **ArcadeMachine** main program and start the UI. + +Running **ArcadeMachine** successfully enters the interface; audio and some games still need adaptation (see **Unfinished & Suggestions**). + +--- + +## 1) Environmental Requirements + +- **Device:** Raspberry Pi (armv7) +- **OS:** Raspberry Pi OS (desktop, capable of running X11) +- **Compiler:** g++ 10.2 (Raspbian) + +**Directory layout** (match this to reduce path differences): + +```text +/opt/arcade/ + arcade-machine/ # Main program + arcade-games/ # Game collections + sk-compat/ # Compatibility headers + sk-shim/ # Dynamic library wrapper +/opt/splashkit-core/ # 2022 version core repository +``` + +--- + +## 2) Get the Repositories + +```bash +# 1) splashkit-core (reverted to around 2022-08) +sudo mkdir -p /opt && sudo chown -R "$USER":"$USER" /opt +git clone https://github.com/ZGT23/splashkit-core.git /opt/splashkit-core +cd /opt/splashkit-core + +# Make sure you're in the 2022-08 commit (we used commit 2e75c14) +git show -s --date=short --pretty='commit %h %ad %s' + +# 2) arcade-machine (your own fork) +git clone https://github.com//arcade-machine.git /opt/arcade/arcade-machine + +# 3) arcade-games (your own fork) +git clone https://github.com//arcade-games.git /opt/arcade/arcade-games +``` + +--- + +## 3) Build the “legacy” SplashKitBackend (backend only) + +```bash +cd /opt/splashkit-core +rm -rf build +cmake -S projects/cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j "$(nproc)" --target SplashKitBackend +sudo cmake --install build +sudo ldconfig +``` + +When completed, you should have: + +``` +/usr/local/lib/libSplashKitBackend.a +/usr/local/include/SplashKitBackend/*.h +``` + +We chose **not** to directly generate the official `libSplashKit.so`, but instead used the shim in the next step to export the legacy API symbols. This is the path we validated on the Pi. + +--- + +## 4) Build the shim dynamic library +_Pack the Backend static library into `libSplashKit.so` and export legacy symbols._ + +```bash +mkdir -p /opt/arcade/sk-shim && cd /opt/arcade/sk-shim + +# CMakeLists.txt +cat > CMakeLists.txt << 'EOF' +cmake_minimum_required(VERSION 3.13) +project(SKShim CXX) + +add_library(SplashKit SHARED empty.cpp) +target_include_directories(SplashKit PRIVATE /usr/local/include/SplashKitBackend) + +# Pack the entire Backend static library to ensure symbols are exported +target_link_libraries(SplashKit PRIVATE -Wl,--whole-archive /usr/local/lib/libSplashKitBackend.a -Wl,--no-whole-archive) +EOF + +# An empty source file +echo "extern \"C\" void __skshim_keep(){}" > empty.cpp + +# Build & Install +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j "$(nproc)" +sudo install -m 755 build/libSplashKit.so /usr/local/lib/libSplashKit.so +sudo ldconfig + +# Verify that legacy database-related symbols are exported (example) +nm -D /usr/local/lib/libSplashKit.so | grep -E 'open_database|run_sql|query_success|has_row|query_column_for_' | head +``` + +You should see exported symbols (mostly namespace-qualified symbol names, sufficient for linking). + +--- + +## 5) Install the header compatibility layer (`sk-compat`) +_Resolves inclusion and call discrepancies in legacy code._ + +We adapted `splashkit.h` to forward to the Backend headers and provided shims for a few legacy functions/macros to avoid overload ambiguity (do **not** `using namespace splashkit_lib;` to prevent duplicate-name conflicts). Place this file at `/opt/arcade/sk-compat/include/splashkit.h`: + +```cpp +#pragma once + +// Only include the Backend header, avoid using-directives to reduce ambiguity +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// -- Type short names: Export commonly used types to the global scope -- // +using splashkit_lib::sprite; +using splashkit_lib::bitmap; +using splashkit_lib::point_2d; +using splashkit_lib::rectangle; +using splashkit_lib::vector_2d; +using splashkit_lib::color; +using splashkit_lib::key_code; + +// -- Color macros maintain historical conventions -- // +#define COLOR_BLACK (splashkit_lib::color_black()) +#define COLOR_WHITE (splashkit_lib::color_white()) + +// -- "Unambiguous forwarding" of commonly used functions -- // +inline void refresh_screen() { splashkit_lib::refresh_screen(); } +inline void refresh_screen(unsigned int /*fps*/) { splashkit_lib::refresh_screen(); } + +inline void draw_text(const std::string &t, const color &c, const std::string &font, int sz, double x, double y) { + splashkit_lib::draw_text(t, c, font, sz, x, y); +} +inline void clear_screen(const color &c) { splashkit_lib::clear_screen(c); } +inline void process_events() { splashkit_lib::process_events(); } +inline bool quit_requested() { return splashkit_lib::quit_requested(); } +inline bool key_down(key_code k){ return splashkit_lib::key_down(k); } +inline bool key_typed(key_code k){ return splashkit_lib::key_typed(k); } + +inline void play_music(const std::string &name){ splashkit_lib::play_music(name); } +inline void stop_music(){ splashkit_lib::stop_music(); } +inline bool music_playing(){ return splashkit_lib::music_playing(); } + +inline void write_line(const std::string &s){ splashkit_lib::write_line(s); } + +// Old resource pack API mapping +inline void load_resource_bundle(const std::string &name, const std::string &path){ + splashkit_lib::load_resource_bundle(name, path); +} +inline void free_resource_bundle(const std::string &name){ + splashkit_lib::free_resource_bundle(name); +} + +// sprites / camera commonly used +using splashkit_lib::sprite_center_point; +using splashkit_lib::sprite_position; +using splashkit_lib::sprite_set_position; +using splashkit_lib::sprite_set_dx; +using splashkit_lib::sprite_set_dy; +using splashkit_lib::create_sprite; +using splashkit_lib::bitmap_named; +using splashkit_lib::music_named; +using splashkit_lib::draw_bitmap; +using splashkit_lib::update_animation; +using splashkit_lib::animation_ended; + +using splashkit_lib::screen_width; +using splashkit_lib::screen_height; +using splashkit_lib::to_screen; +using splashkit_lib::to_screen_x; +using splashkit_lib::to_screen_y; +using splashkit_lib::point_on_screen; +using splashkit_lib::rect_on_screen; +using splashkit_lib::camera_x; +using splashkit_lib::camera_y; +using splashkit_lib::move_camera_to; +using splashkit_lib::set_camera_x; +using splashkit_lib::set_camera_y; + +// window related +using splashkit_lib::open_window; +using splashkit_lib::hide_mouse; +using splashkit_lib::window_toggle_border; +using splashkit_lib::window_toggle_fullscreen; +using splashkit_lib::window_is_fullscreen; +``` + +> **Notes** +> +> - We wrap the “ambiguous” functions with `inline` and avoid broad `using` to reduce naming conflicts. +> - The `refresh_screen(unsigned int)` overload is key: it swallows old calls like `refresh_screen(60)` and forwards to the parameterless version, avoiding the “overload ambiguity” compile error. +> - If more functions/enums are missing, add similar inline forwarders here. + +--- + +## 6) Compile ArcadeMachine + +```bash +cd /opt/arcade/arcade-machine +make clean 2>/dev/null || true + +# Key: Include sk-compat headers and link against libSplashKit.so +make \ + CPPFLAGS="-I/opt/arcade/sk-compat/include -I/usr/local/include/SplashKitBackend" \ + CXXFLAGS="-Iinclude -I/opt/arcade/sk-compat/include -I/usr/local/include/SplashKitBackend" \ + LDFLAGS="-lstdc++fs -lSplashKit" +``` + +Successful compilation should produce `./ArcadeMachine` and: + +```bash +ldd ./ArcadeMachine | grep SplashKit +# Should show: /usr/local/lib/libSplashKit.so +``` + +--- + +## 7) Run ArcadeMachine (runtime environment) + +Run inside a **graphical desktop session** (not pure SSH): + +```bash +cd /opt/arcade/arcade-machine + +# If the audio device is missing, use the "silent driver" to avoid errors. +export SDL_AUDIODRIVER=dummy +./ArcadeMachine +``` + +**Common symptoms & fixes** + +- **Font/Audio warnings:** Ignore when using the dummy audio driver. Ensure `resources/fonts/*.ttf` exist and are included in the resource bundle. +- **Menu freeze on first entry:** Likely due to `arcade-games` cloning/indexing or missing resources—clone `/opt/arcade/arcade-games` beforehand and confirm resources are in place. + +--- + +## 8) Compile and run a C++ game (example: _BelowTheSurface_) + +This repository may not ship with a Makefile (in our version). You can compile quickly with one command: + +```bash +cd /opt/arcade/arcade-games/games/BelowTheSurface + +# Single-file example (program.cpp with include/ ) +g++ -std=gnu++14 \ + -I include \ + -I /opt/arcade/sk-compat/include \ + -I /usr/local/include/SplashKitBackend \ + program.cpp \ + -lSplashKit -lstdc++fs -O3 \ + -o below_the_surface + +# Run (dummy audio still recommended) +export SDL_AUDIODRIVER=dummy +./below_the_surface +``` + +If there are multiple `.cpp` files, append them to the command, or create a minimal Makefile with the same include path and `-lSplashKit` link. + +--- + +## 9) Targeted fixes we applied + +- **`refresh_screen(60)` overload ambiguity:** Provided `inline void refresh_screen(unsigned int)` in `sk-compat/splashkit.h`. +- **Missing `sprite_center_point` / `music_playing` symbols:** Resolved via `using` or inline forwarders to `splashkit_lib::...` in `sk-compat`. Add more as needed. +- **Database symbols missing at link:** Pack `libSplashKitBackend.a` via shim `libSplashKit.so` to satisfy old-style symbols. +- **No audio device:** `export SDL_AUDIODRIVER=dummy` to avoid “Attempting to load music when audio is closed”. + +--- + +## 10) Unfinished & Constructive Suggestions + +### A. Uniform `sk-compat` mapping +Systematically review the legacy API and provide items one by one in `sk-compat/splashkit.h`: + +- `write_line` / `delay` / timers family +- Overloads for bundles (e.g., `string_view`) +- Keep exports focused on **types & constants**; use **inline forwarders** for functions to avoid conflicts +- Consider a separate repo/subdir for `sk-compat` and include it in build-product testing + +### B. Provide a common Makefile template for `arcade-games` + +Place a minimal `Makefile` in each game directory so ArcadeMachine can run `make -C ` before launching (avoids a blank screen): + +```make +CXX := g++ +CXXFLAGS := -std=gnu++14 -O3 -I include -I /opt/arcade/sk-compat/include -I /usr/local/include/SplashKitBackend +LDFLAGS := -lSplashKit -lstdc++fs + +SRC := $(wildcard *.cpp src/*.cpp) +BIN := game + +all: $(BIN) + +$(BIN): $(SRC) +\t$(CXX) $(CXXFLAGS) $^ -o $@ $(LDFLAGS) + +clean: +\trm -f $(BIN) +``` + +### C. Robustness of resource bundles + +- Verify resource files exist **before** launching a game; show a clear UI prompt if missing instead of a blank screen. +- On silent devices, failures in `play_music`/`load_sound_effect` should warn but **not** interrupt the process. + +### D. Window/Display environment detection + +We encountered: `displayIndex must be in the range 0 - -1` — happens when there is **no desktop session** or `DISPLAY` is unset. Detect `SDL_VIDEODRIVER` / `DISPLAY` at launch and provide a helpful prompt in SSH environments (or fallback to dummy video). + +### E. Script the shim build + +Automate the three-step CMake build (`scripts/build-shim.sh`) for one-click install to lower entry barriers. + +### F. CI verification + +Set up a minimal GitHub Action (for ARM via cross/runner) to run header and link checks to avoid future breakages after system upgrades. + +--- + +_This Markdown was adapted directly from the original operation manual for the Pi3 test machine to preserve readability while making it easy to follow and copy-paste commands._ From 9910a3168e655696b510d7f634097b85ac0ed60e Mon Sep 17 00:00:00 2001 From: seventhback777 <903157209@qq.com> Date: Thu, 2 Apr 2026 01:15:16 +1100 Subject: [PATCH 4/7] remove embedded games repo, add to gitignore --- .gitignore | 2 + HANDOFF.md | 215 ++++++++++++++++++++++++++++++++ include/AboutScreen.h | 24 ++-- include/ConfigData.h | 2 +- include/Configuration.h | 3 + include/Database.h | 24 ++++ include/Helper.h | 11 ++ include/Menu.h | 14 ++- include/Option.h | 3 +- include/Process.h | 3 +- include/Rating.h | 2 +- logs.txt | 27 ++++ resources/bundles/resources.txt | 8 +- src/AboutScreen.cpp | 181 ++++++++++++++++----------- src/ArcadeMachine.cpp | 54 ++++++-- src/Button.cpp | 2 +- src/ConfigData.cpp | 74 +++++++++-- src/Menu.cpp | 139 +++++++++++++++++---- src/Option.cpp | 21 +++- src/Process.cpp | 62 +++++++-- src/program.cpp | 57 ++++++++- 21 files changed, 771 insertions(+), 157 deletions(-) create mode 100644 HANDOFF.md create mode 100644 logs.txt diff --git a/.gitignore b/.gitignore index 275e8b6..f6f7861 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ games/* **/*.o ArcadeMachine **/.DS_Store +games/ +games/ diff --git a/HANDOFF.md b/HANDOFF.md new file mode 100644 index 0000000..e937491 --- /dev/null +++ b/HANDOFF.md @@ -0,0 +1,215 @@ +# Arcade Machine — Engineering Handoff Document + +> Last updated: 2026-04-01 (observability work in progress) +> Original session: Claude Sonnet 4.6 via VS Code extension +> Recipient: Next AI assistant (ChatGPT Codex or new Claude session) + +--- + +## Project Overview + +C++ arcade machine frontend built on the **SplashKit** game framework (SDL2-based). +Runs on a **Raspberry Pi 3B** (Linux ARM), displays a carousel game menu on a TV, +launches games as child processes, tracks stats via SQLite. + +**Working directory on RPi:** `~/arcade-machine/` +**Games directory:** `~/arcade-machine/games/games/` +**Connection:** SSH from developer laptop + +--- + +## Architecture Summary + +``` +program.cpp + └── ArcadeMachine + ├── Menu — carousel UI, game launching (src/Menu.cpp) + │ └── ButtonNode — circular doubly-linked list of game buttons + ├── Option — settings screen (src/Option.cpp) + ├── AboutScreen — about/credits screen + └── Rating — star rating system + +Game discovery chain: + Helper::ConfigDataList() + → getConfigFiles("./games/games") [scans for config.txt files] + → ConfigData(configFile) [parses each config.txt] + → Menu::createButtons() [loads images, creates sprites] + +Game launch chain (Linux): + Menu::carouselHandler() + → ConfigData::getExecutablePath() [finds binary, two paths — see below] + → Menu::startGame(path) + → spawnProcess(dir, file) [fork() + popen()] + → Menu::checkGameExit() [polls processRunning() each frame] +``` + +### Key files + +| File | Role | +|------|------| +| `src/program.cpp` | Entry point | +| `src/ArcadeMachine.cpp` | Top-level controller | +| `src/Menu.cpp` | Carousel menu + game launching | +| `src/Process.cpp` | Linux fork/popen process management | +| `src/ConfigData.cpp` | config.txt parser + git clone/pull | +| `src/Option.cpp` | Options/settings screen | +| `include/Helper.h` | Game discovery, filesystem scanning | +| `include/Rating.h` | Star rating value object | +| `resources/bundles/resources.txt` | SplashKit resource bundle manifest | + +### Platform macros (defined in Configuration.h) + +- `ARCADE_MACHINE_OS` — `"linux"` / `"windows"` / `"macos"` +- `ARCADE_MACHINE_INSTRUCTION_SET` — `"arm"` / `"aarch64"` / `"x86_64"` +- `ARCADE_MACHINE_BINARY_EXT` — `""` (Linux/Mac) / `".exe"` (Windows) +- `ARCADE_MACHINE_PATH_SEP` — `"/"` (Linux/Mac) / `"\\"` (Windows) + +--- + +## Game Executable Resolution (`ConfigData::getExecutablePath`) + +Two-path fallback logic in `src/ConfigData.cpp:233`: + +**Path 1 — Auto build (preferred):** +Looks for `{game_folder}/builds/linux-arm` (or `linux-aarch64`, `windows-x86_64.exe`, etc.) +No config.txt changes needed; file must exist with correct naming convention. + +**Path 2 — Manual config (fallback):** +Reads `lin-exe=` field from `config.txt`. +Throws `std::runtime_error` if empty or file not found. +Exception IS caught in `Menu.cpp` and printed via `write_line()`. + +**Current state:** All 9 games have empty `Linux Bin` — neither path works for any game. +This is a content problem (games not compiled for Linux/ARM), not a code bug. + +--- + +## Current Task Priority + +### Priority 1 — Observability (IN PROGRESS) + +All output uses `std::cerr << "[Module] message" << std::endl` format. +`std::endl` flushes immediately — safe even if program crashes mid-run. +Visible in SSH terminal when running `DISPLAY=:0 ./ArcadeMachine`. + +#### DONE + +**`src/Process.cpp`** — fully instrumented: +- `fork()` returns -1 → `[Process] fork failed: ` +- Parent after successful fork → `[Process] spawned game: / (PID: )` +- `chdir()` fails → `[Process] chdir failed: ` then exits child +- `popen()` fails → `[Process] popen failed: ` then exits child + +**`src/Menu.cpp` — `createButtons()`** — image loading instrumented: +- `image=` field empty in config.txt → `[Menu] image load failed (empty path): ` +- File not found on disk → `[Menu] image load failed (file not found): <path>` +- SplashKit failed to load → `[Menu] image load failed (splashkit error): <path>` +- Success → `[Menu] image loaded: <path>` + +**`src/Menu.cpp` — `startGame()` + `drawMenuPage()` + `checkGameExit()`** — game launch全流程: +- 启动前 → `[Menu] starting game: <file> from <path>` +- fork失败 → `[Menu] failed to start game: fork failed` + 屏幕红色错误3秒 +- fork成功 → `[Menu] game process spawned (PID: <pid>)` + 屏幕动态"Starting <title>..." +- 游戏3秒内退出 → `[Menu] game crashed on startup` + 屏幕红色崩溃信息3秒 +- 正常退出 → `[Menu] game process ended: <title> (PID: <pid>)` + +**`src/ConfigData.cpp` — `getFromGit()`** — git 操作全程有输出: +- 开始前 → `[Git] cloning/pulling: <url> → <dir>` +- 结束后 → `[Git] clone/pull succeeded/failed: <dir>` + +**`include/Helper.h` — `getConfigFiles()`** — 扫描结果: +- 扫描结束 → `[Helper] found N config files in <dir>` + +**`src/ConfigData.cpp` — `ConfigData` 构造函数** — 关键字段缺失检查: +- `title` 为空 → `[Config] missing title: <config_path>` +- `image` 为空 → `[Config] missing image: <config_path>` +- `linux-bin` 为空 → `[Config] missing linux-bin: <config_path>` + +**`src/program.cpp`** — 启动资源检查: +- `resources.txt` 不存在 → `[Bundle] resources.txt not found` +- 资源包加载结果 → `[Bundle] resource bundle loaded/failed` +- 逐个检查所有 bitmap 和 font → `[Bundle] bitmap not loaded: <name>` +- 汇总 → `[Bundle] N resource(s) failed to load` + +**`src/ArcadeMachine.cpp` — `playSplashKitIntro()`** — git 失败不再死循环: +- 最多重试3次,每次等待30秒 +- 每次在屏幕和终端输出当前是第几次尝试 +- 3次全失败 → 屏幕显示错误5秒后 `exit(EXIT_FAILURE)` + +**`src/ConfigData.cpp` — `openFile()`** — 报错补充文件路径: +- `[Config] failed to open file: <path>` + +#### PENDING + +无。Priority 1 可观测性工作全部完成。 + +### Priority 2 — Bug fixes (DONE — 16 bugs fixed, not yet deployed to RPi) +See section below. + +### Priority 3 — Game loading +Games need compiled Linux ARM binaries. Either: +- Pre-compile and place in `{game_folder}/builds/linux-arm` (or `linux-aarch64`) +- Or add `lin-exe=` to each game's `config.txt` + +Some games reportedly have pre-built versions; needs investigation of actual directory structure. + +--- + +## Completed Bug Fixes (not yet synced to RPi) + +All fixes are in the working directory on the developer's laptop. +**None of these have been deployed to the RPi yet.** + +| # | File | Problem | Fix | +|---|------|---------|-----| +| 1 | `include/Helper.h` | `getFolderName()` returned trailing slash → double-slash paths | Strip trailing `/` or `\` | +| 2 | `include/Helper.h` | `getConfigFiles()` no existence check → crash if `./games/games` missing | Add `fs::exists()` guard | +| 3 | `src/ConfigData.cpp` | Hardcoded `#include <experimental/filesystem>` → compile failure on C++17 | Conditional include | +| 4 | `src/ConfigData.cpp` | `getFromGit()` always returned `true` even on git failure | Check directory exists after clone | +| 5 | `src/Menu.cpp` | `createButtons()` called `create_sprite` without prior `load_bitmap` | Add `load_bitmap(image, image)` before `create_sprite` | +| 6 | `src/Menu.cpp` | Windows launch: dangling pointer from `.c_str()` on temporary string | Use persistent `std::string` + `std::vector<char>` | +| 7 | `src/Menu.cpp` | Windows launch: duplicate pre-fade variable assignment | Remove duplicate line | +| 8 | `src/Menu.cpp` | Both constructors: `m_tip` uninitialized | Add `m_tip = nullptr` | +| 9 | `src/Menu.cpp` | `drawMenuPage()`: unconditional `m_tip->draw()` → null dereference | Guard with `if (!m_inGame && m_tip)` | +| 10 | `src/Menu.cpp` | Destructor: circular `ButtonNode` list never freed → memory leak | Add full list traversal and delete | +| 11 | `include/Menu.h` | Dead `LPCSTR m_gamePath`, `LPSTR m_gameExe`, `LPCSTR m_gameDir` members | Remove | +| 12 | `src/Option.cpp` | `y_pos` uninitialized → undefined behaviour | Add `= 0` | +| 13 | `src/Option.cpp` | Missing destructor → `ButtonNode` list leak | Add `~Option()` with list cleanup | +| 14 | `include/Option.h` | Circular `#include "ArcadeMachine.h"` (unused) | Remove | +| 15 | `src/program.cpp` | `ArcadeMachine` constructed before `open_window()` → `create_sprite` called without render context | Move `open_window` + `window_toggle_border` before constructor | +| 16 | `resources/bundles/resources.txt` | 4 music entries had leading spaces in names → SplashKit lookup fails | Remove spaces: `MUSIC, 1, 1.mp3` → `MUSIC,1,1.mp3` | + +--- + +## Known Issues / Not Yet Fixed + +### All games — No Linux executables configured +All 9 games have empty `Linux Bin` in their config.txt. +`getExecutablePath()` throws `std::runtime_error` which IS caught and printed via `write_line()`. +Games with pre-built binaries need path configured or binaries moved to `builds/linux-arm` (or `linux-aarch64`). + +--- + +## Deployment Notes + +Changes are on developer laptop only. To deploy to RPi: +```bash +# Option A: rsync +rsync -av --exclude='.git' ~/path/to/arcade-machine/ pi@192.168.x.x:~/arcade-machine/ + +# Option B: git commit + push + pull on RPi +``` +RPi is accessed via SSH. Program launched with: +```bash +DISPLAY=:0 ./ArcadeMachine +``` + +--- + +## Collaboration Style Notes + +- User has C++ basics, not deeply familiar with this codebase +- Teach concepts as we go, don't just silently fix everything +- One-shot fixes preferred — find ALL issues in a file before reporting +- RPi is physical hardware; can't iterate quickly → get fixes right before deploying +- Current session partner: Claude Sonnet 4.6 (VS Code extension) diff --git a/include/AboutScreen.h b/include/AboutScreen.h index 979dfc9..6a77799 100644 --- a/include/AboutScreen.h +++ b/include/AboutScreen.h @@ -9,20 +9,20 @@ struct s_star { double y; double distance; color c; -}; +};//定义结构体星星 class AboutScreen { private: - bool m_shouldQuit; - unsigned long long m_ticker; - std::string m_title; - double m_titleX; - double m_titleEnd; - std::vector<struct s_star> m_stars; - int m_contributorsIndex; - int m_contributorTicker; - std::vector<std::string> m_contributors; - std::vector<std::string> m_linesOfCode; + bool m_shouldQuit;//退出标识 + unsigned long long m_ticker;//节拍器 + std::string m_title;//标题 + double m_titleX;//标题横向位置 + double m_titleEnd;//标题结束横向位置 + std::vector<struct s_star> m_stars;//m_stars是一个自动变长且存储着结构体s_star的数组 + int m_contributorsIndex;//贡献者索引 + int m_contributorTicker;//贡献者节拍器 + std::vector<std::string> m_contributors;//存储着贡献者名字的m_contributors + std::vector<std::string> m_linesOfCode;//存储着贡献者代码数量的m_linesOfCode std::vector<std::string> m_gitContributions; void readInput(); @@ -39,7 +39,7 @@ class AboutScreen { void renderContributor(); void onExit(); void loop(); - +//声明函数方法 public: AboutScreen(); void main(); diff --git a/include/ConfigData.h b/include/ConfigData.h index 348cc99..77d4015 100644 --- a/include/ConfigData.h +++ b/include/ConfigData.h @@ -63,7 +63,7 @@ class ConfigData //Setters: auto setId(int &i) { m_id = i; } - auto setFolder(std::string &dir) { m_folder = dir; } + void setFolder(std::string &dir); // Getters: auto id() const -> const int& { return m_id; } auto repo() const -> const std::string& { return m_repo; } diff --git a/include/Configuration.h b/include/Configuration.h index 537c24f..9dfc428 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -5,6 +5,9 @@ #define ARCADE_MACHINE_RES_X 1920 * ARCADE_MACHINE_SCALING_FACTOR #define ARCADE_MACHINE_RES_Y 1080 * ARCADE_MACHINE_SCALING_FACTOR +#define ROWS 7 +#define COLS 15 + #ifdef _WIN32 #define ARCADE_MACHINE_PATH_SEP "\\" #else diff --git a/include/Database.h b/include/Database.h index bc91bd9..be3b2db 100644 --- a/include/Database.h +++ b/include/Database.h @@ -9,6 +9,30 @@ #include <map> #include <tuple> +// --------------------------------------------------------------------------- +// TEMPORARY: SplashKit database API is missing on Windows builds. +// These stubs allow the project to compile on Windows. On Linux/RPi, +// SplashKit provides the real API so no stubs are needed. +// TODO: Rewrite Database.h using raw SQLite3 (-lsqlite3) to restore functionality. +// --------------------------------------------------------------------------- +#ifdef _WIN32 +struct database {}; +struct query_result {}; +inline database open_database(std::string, std::string) { return {}; } +inline void free_database(database) {} +inline query_result run_sql(database, std::string) { return {}; } +inline bool query_success(query_result) { return false; } +inline bool has_row(query_result) { return false; } +inline bool get_next_row(query_result) { return false; } +inline void free_all_query_results() {} +inline int query_column_count(query_result) { return 0; } +inline std::string query_column_for_string(query_result, int) { return ""; } +inline double query_column_for_double(query_result, int) { return 0.0; } +inline int query_column_for_int(query_result, int) { return 0; } +inline std::string query_type_of_col(query_result, int) { return "NULL"; } +#endif +// --------------------------------------------------------------------------- + class Database { private: std::string m_databaseName; diff --git a/include/Helper.h b/include/Helper.h index 1f67fb9..3243f6d 100644 --- a/include/Helper.h +++ b/include/Helper.h @@ -32,6 +32,9 @@ class Helper { string getFolderName(string entryPath) { string dir = fs::path(entryPath).remove_filename().generic_string(); + // Remove trailing slash to prevent double-slash when concatenating paths later + if (!dir.empty() && (dir.back() == '/' || dir.back() == '\\')) + dir.pop_back(); std::cout << "Game-Directory Path: " << dir << "\n"; return dir; } @@ -46,6 +49,12 @@ class Helper { { vector<string> files; + if (!fs::exists(dir) || !fs::is_directory(dir)) + { + std::cerr << "Warning: Games directory not found: " << dir << "\n"; + return files; + } + for (const auto & entry : fs::recursive_directory_iterator(dir)) { if (entry.path().filename() == "config.txt") @@ -55,6 +64,8 @@ class Helper { } } + std::cerr << "[Helper] found " << files.size() << " config files in " << dir << std::endl; + return files; } diff --git a/include/Menu.h b/include/Menu.h index 6e0d7a5..df5e8f0 100644 --- a/include/Menu.h +++ b/include/Menu.h @@ -36,12 +36,6 @@ class Menu { PROCESS_INFORMATION m_processInfo; // Unsigned int to store exit info DWORD m_exitCode; - // Holds the game path of selected game - LPCSTR m_gamePath; - // Holds the executable of selected game - LPSTR m_gameExe; - // Holds the game directory of selected game - LPCSTR m_gameDir; // m_handle for game window. HWND m_handle; #else @@ -79,6 +73,14 @@ class Menu { // Determines when game has started. bool m_gameStarted = false; + // True from launch attempt until confirmed running or failed. + bool m_launching = false; + // Timestamp (ms) when m_inGame was set — used to detect crashes. + unsigned int m_launchTime = 0; + // Holds error message to display after launch failure or crash. + std::string m_launchError = ""; + // Exit code of last game process (0 = normal, non-zero = crash). + int m_lastExitCode = 0; // Starting position of button x. const int m_posX = 700; // Position of button y. diff --git a/include/Option.h b/include/Option.h index dcd4824..f2b5df1 100644 --- a/include/Option.h +++ b/include/Option.h @@ -2,11 +2,11 @@ #define ARCADE_MACHINE_OPTION_H #include "AboutScreen.h" +#include "Configuration.h" #include "splashkit.h" #include <string> #include "GridLayout.h" #include "Button.h" -#include "ArcadeMachine.h" #include "Selector.h" #include "OptionsScreenButton.h" #include "Audio.h" @@ -34,6 +34,7 @@ class Option public: Option(); + ~Option(); void createOptionsButtons(); void drawOptionsMenu(); diff --git a/include/Process.h b/include/Process.h index 04337df..bfffe8d 100644 --- a/include/Process.h +++ b/include/Process.h @@ -4,6 +4,7 @@ #include <unistd.h> pid_t spawnProcess(std::string directory, std::string fileName); -bool processRunning(pid_t processId); +bool processRunning(pid_t processId, int &exitCode); +void killProcess(pid_t processId); #endif \ No newline at end of file diff --git a/include/Rating.h b/include/Rating.h index 3fcfc92..51e5395 100644 --- a/include/Rating.h +++ b/include/Rating.h @@ -38,7 +38,7 @@ class Rating if (key_typed(LEFT_KEY)) { --_rating; - if (_rating < 0) + if (_rating < 1) _rating = _maxRating; updateGrid(); } diff --git a/logs.txt b/logs.txt new file mode 100644 index 0000000..96ee372 --- /dev/null +++ b/logs.txt @@ -0,0 +1,27 @@ +[Bundle] resource bundle loaded successfully +[Bundle] all bitmaps and fonts loaded successfully +[Git] Connecting... (attempt 1/3) +[Git] pulling: https://github.com/thoth-tech/arcade-games.git → games +From https://github.com/thoth-tech/arcade-games + * branch HEAD -> FETCH_HEAD +[Git] pull succeeded: games +[Git] games loaded successfully on attempt 1 +[Helper] found 9 config files in ./games/games +[Config] no builds/ directory: ./games/games/2dRacer +[Config] Below The Surface supports: Linux ARM, Linux x86, Windows +[Config] no builds/ directory: ./games/games/car-race +[Config] Homemade Pong supports: Linux ARM, Linux x86, Windows +[Config] no builds/ directory: ./games/games/Pingpong +[Config] builds/ is empty: ./games/games/Runner_Dash +[Config] no builds/ directory: ./games/games/SkySurge +[Config] no builds/ directory: ./games/games/splashkit mario +[Config] Venture Adventure supports: Linux ARM, Linux x86, Windows +[Menu] image loaded: ./games/games/2dRacer/2D-Title.jpg +[Menu] image loaded: ./games/games/BelowTheSurface/title_card.png +[Menu] image loaded: ./games/games/car-race/images/car-race-config-image.png +[Menu] image loaded: ./games/games/HomemadePong/images/pong.png +[Menu] image loaded: ./games/games/Pingpong/images/Pingpong-config-image.png +[Menu] image loaded: ./games/games/Runner_Dash/TitleImage.jpg +[Menu] image loaded: ./games/games/SkySurge/images/SkySurge-config-image.png +[Menu] image loaded: ./games/games/splashkit mario/sk-mario-config-image.png +[Menu] image loaded: ./games/games/VentureAdventure/Resources/images/VenAd.png diff --git a/resources/bundles/resources.txt b/resources/bundles/resources.txt index a632c5d..1e3bbb5 100644 --- a/resources/bundles/resources.txt +++ b/resources/bundles/resources.txt @@ -62,10 +62,10 @@ SOUND,intro_start,completed.wav // Music MUSIC,music_mainmenu,main_menu.mp3 -MUSIC, 1, 1.mp3 -MUSIC, 2, 2.mp3 -MUSIC, 3, 3.mp3 -MUSIC, music_about, insert-no-coins.ogg +MUSIC,1,1.mp3 +MUSIC,2,2.mp3 +MUSIC,3,3.mp3 +MUSIC,music_about,insert-no-coins.ogg // Animations ANIM,info-script,information.txt diff --git a/src/AboutScreen.cpp b/src/AboutScreen.cpp index 37a339c..96dec9b 100644 --- a/src/AboutScreen.cpp +++ b/src/AboutScreen.cpp @@ -30,71 +30,87 @@ static const char *description[] = { static const std::string createdBy = "Created by"; AboutScreen::AboutScreen() { - this->m_shouldQuit = false; - this->m_titleX = ARCADE_MACHINE_RES_X; - this->m_title = std::string(title); + this->m_shouldQuit = false;//初始化退出标志为False + this->m_titleX = ARCADE_MACHINE_RES_X;//将标题的X位置定位在屏幕最右边 + this->m_title = std::string(title);//将m_title变成string风格的字符串,方便进行各种操作,如lenth() this->m_titleEnd = ((TITLE_FONT_CHAR_WIDTH * this->m_title.length())); this->m_titleEnd *= -1; - this->m_stars = std::vector<struct s_star>(); + /*这里的设计涉及到打印逻辑,在打印标题的时候,每打印一个字符,m_titleX的位置就加一个TITLE_FONT_CHAR_WIDTH,所以标题的总长度应为TITLE_FONT_CHAR_WIDTH * this->m_title.length() + 也就是字符长度(m_title.length())乘以字符宽度(TITLE_FONT_CHAR_WIDTH),而当字符从右往左完全滚动出屏幕之时,第一个字符的X位置应该刚好是-标题总长度,因为此时 + 最右边的字符坐标应该为0,所以m_titleEnd的位置应该是-标题总长度=-((TITLE_FONT_CHAR_WIDTH * this->m_title.length()));,也就是37行m_titleEnd的最终值 + */ + this->m_stars = std::vector<struct s_star>();//初始化数组m_stars this->m_contributorsIndex = 0; this->m_contributorTicker = 0; this->m_ticker = 0; for (int i=0; i<STAR_COUNT; ++i) { struct s_star star; - star.x = (rand() % (ARCADE_MACHINE_RES_X - 0 + 1) + 0); - star.y = (rand() % (ARCADE_MACHINE_RES_Y - 0 + 1) + 0); + star.x = (rand() % (ARCADE_MACHINE_RES_X - 0 + 1) + 0);//随机生成星星的X位置 + star.y = (rand() % (ARCADE_MACHINE_RES_Y - 0 + 1) + 0);//随机生成星星的Y位置 + /*这是一个经典限制取值范围的公式:(rand() % (max - min +1)) + min,前面的rand() % (max - min +1)能保证得出的余值范围是0 到 max - min,因为rand()函数取值 + 够广够多,则在这种情况下,a % b 的取值范围是[0,b-1],所以rand() % (ARCADE_MACHINE_RES_X - 0 + 1)的范围是在0到ARCADE_MACHINE_RES_X - 0,再加一个0就是 + 0到ARCADE_MACHINE_RES_X,Y方向上的计算同理 + */ star.distance = (rand() % (DISTANCE_SHIFT - 1 + 1) + 1); - + //!!! double brightness = (rand() % (80 - 40) + 40); + //同理这个亮度取值范围应该是在40到79 brightness /= 100; - + //规范brightness的值在0-1之间 star.c.a = brightness; star.c.r = 1; star.c.g = 1; star.c.b = 1; - + //argb调色,a为我们取的brightness this->m_stars.push_back(star); } - - this->m_contributors = std::vector<std::string>(); + //循环生成所有结构体(1024颗)s_star星星的循环,包括每个星星的X,Y位置,亮度,distance,并把这些结构体push进数组m_stars + + this->m_contributors = std::vector<std::string>();//初始化数组m_contributors std::ifstream contributors("stats" ARCADE_MACHINE_PATH_SEP "contributors.txt"); + //使用ifstream(输入文件流)输入贡献者名单文件 ARCADE_MACHINE_PATH_SEP是为了应对不同系统风格的分隔符 std::string line; while (std::getline(contributors, line)) { if (line.length() > 0) this->m_contributors.push_back(line); } contributors.close(); - - this->m_linesOfCode = std::vector<std::string>(); + //按行读取贡献者名单,并把贡献者名字加入到m_contributors数组里 + + this->m_linesOfCode = std::vector<std::string>();//初始化m_linesOfCode数组 std::ifstream linesOfCode("stats" ARCADE_MACHINE_PATH_SEP "lines-of-code.txt"); while (std::getline(linesOfCode, line)) { if (line.length() > 0) this->m_linesOfCode.push_back(line); } - + //按行读取贡献者代码数量文件,并把贡献者代码数量加入到m_linesOfCode数组里 + this->m_gitContributions = std::vector<std::string>(); std::ifstream contributions("stats" ARCADE_MACHINE_PATH_SEP "git.txt"); while (std::getline(contributions, line)) { if (line.length() > 0) this->m_gitContributions.push_back(line); } - + //同上两个代码块 } +//初始化构造函数 void AboutScreen::onExit() { if (music_playing()) stop_music(); } +//在退出以后关闭音乐播放的函数 void AboutScreen::readInput() { if (quit_requested() || key_down(ESCAPE_KEY)) this->m_shouldQuit = true; } +//将m_shouldQuit标识改成true的函数(如果用户请求) void AboutScreen::tick() { - this->shiftTitle(); - this->shiftStars(); + this->shiftTitle();//更新标题位置 + this->shiftStars();//更新星星位置 this->tickContributor(); // Every 1/4 second. @@ -102,65 +118,70 @@ void AboutScreen::tick() { if (! music_playing()) play_music("music_about"); } - - this->m_ticker++; + //每0.25秒检查一次,因为我们是60fps,15帧近似与0.25秒 + this->m_ticker++;//增加帧数记录 } +//每帧更新函数,包括位置音乐计时 void AboutScreen::render() { - clear_screen(COLOR_BLACK); + clear_screen(COLOR_BLACK);//清理屏幕使其成为黑色 - this->renderStars(); - this->renderTitle(); - this->renderDescription(); - this->renderContributor(); + this->renderStars();//渲染星星 + this->renderTitle();//渲染标题 + this->renderDescription();//渲染描述 + this->renderContributor();//渲染贡献者 - refresh_screen(); + refresh_screen();//刷新屏幕展现渲染结果 } - +//每调用一次依次执行:清屏,渲染星星,渲染标题,渲染描述,渲染贡献者,刷新屏幕,后渲染者在先渲染者之上 void AboutScreen::shiftTitle() { this->m_titleX -= 6; if (this->m_titleX < this->m_titleEnd) this->m_titleX = ARCADE_MACHINE_RES_X; } +//每次调用都让m_titleX向左平移6个单位,当m_titleX<m_titleEnd时,让m_titleX重新回到最右边 color AboutScreen::getRainbowShade(double x) { color c; - double rd = (double)ARCADE_MACHINE_RES_X / 16; - double gd = (double)ARCADE_MACHINE_RES_X / 10; - double bd = (double)ARCADE_MACHINE_RES_X / 6; + double rd = (double)ARCADE_MACHINE_RES_X / 16;//红色影响因子,这个值越小,sin(x/rd)变化就越大,所以红色就变得越快,这个值最小 + double gd = (double)ARCADE_MACHINE_RES_X / 10;//绿色影响因子 + double bd = (double)ARCADE_MACHINE_RES_X / 6;//蓝色影响因子,这个值最大 double p = sin(x / rd) * 0.5; double g = sin(x / gd) * 0.5; double b = sin(x / bd) * 0.5; - + //将三原色的pgb取值范围处理成[-0.5,0.5] c.r = 0.5 + p; c.g = 0.5 + g; c.b = 0.5 + b; - + //将三原色渲染的变量范围处理成正确的[0,1],所以最后结果是红色变化得最快,蓝色变化的最慢,所以因为三原色变化速度的差值,会出现彩虹色。 return c; } - +//通过三原色变化因子的大小和sin函数实现三原色变化速度的差异,所以每次会生成不同颜色并且返回,看起来像彩虹 void AboutScreen::renderTitle() { - double x = this->m_titleX; + double x = this->m_titleX;//一开始x=this->m_titleX也就是在屏幕最右方 for (int i=0; i<this->m_title.length(); ++i) { double y = TITLE_FONT_Y + (sin(x / 80) * 115); + //通过sin里带入X值,实现标题y的上下浮动,且因为每个字符的X值不同,所以浮动高度不同,又由于sin函数的特性,会导致看起来像波浪形状,浮动范围在上下115 double fontSize = TITLE_FONT_SIZE + (sin(x / 120) * 8); - + //同理,但是这里是为了改变字符大小,字符大小波动在上下8 color c = this->getRainbowShade(x); - + //getRainbowShade(x)通过随机字符颜色 draw_text( - this->m_title.substr(i, 1), - c, - "font_about", - fontSize, - x, - y + this->m_title.substr(i, 1), //单独取每个字符渲染 + c,//字符颜色 + "font_about",//字符字体 + fontSize,//字符大小 + x,//字符X位置 + y//字符Y位置 ); - x += TITLE_FONT_CHAR_WIDTH; + x += TITLE_FONT_CHAR_WIDTH;//两个字符间距 } + //(一个一个!)地渲染字符 } +//渲染标题,通过sin函数和getRainbowShade()还有一个一个字渲染的方法实现标题波浪形运行,字体忽大忽小,颜色彩虹的效果 void AboutScreen::shiftStars() { for (int i=0; i<this->m_stars.size(); ++i) { @@ -169,29 +190,35 @@ void AboutScreen::shiftStars() { this->m_stars[i].x = -10; } } +//每次调用shiftStars()时,为每一颗星星的X加上随机数distance,如果加完以后星星超出右边界,则把星星放到-10的位置,然后再飘回来,达到动态效果 void AboutScreen::renderStars() { for (auto star : this->m_stars) fill_rectangle(star.c, star.x, star.y, star.distance / 2, star.distance / 4); } +//渲染星星的函数fill_rectangle()最后两项是矩形宽高,所以这个渲染会出现近的星星更大更快,远的星星更小更慢的情况,所以这里的distance是深度/速度等级 void AboutScreen::renderDescription() { - double offset = sin(((double)this->m_ticker / 16)) * 12; - double offsetX = sin((double)this->m_ticker / 24) * 19; - double y = 480 + offset; - double x = 140 + offsetX; + double offset = sin(((double)this->m_ticker / 16)) * 12;//计算X方向波动的影响因子,范围在-12到12个像素点 + double offsetX = sin((double)this->m_ticker / 24) * 19;//计算Y方向波动的影响因子,范围在-12到12个像素点 + //计算XY方向波动的影响因子 + double y = 480 + offset;//计算Y方向总共的波动值 + double x = 140 + offsetX;//计算X方向总共的波动值 + //引入sin主导的波动影响因子,使得文本描述会周期性波动 int maxIOffset = 0; for (int i=0; i<sizeof(description) / sizeof(description[0]); ++i) { maxIOffset = (i * 32); draw_text(description[i], COLOR_WHITE, "font_about", 18, x, y + maxIOffset); } - + /*这里的description是一个指针数组,存放着每行描述的指针,因此我们用指针数组大小除以指针数组的每个元素大小,得到总的元素个数,也就是描述行数,然后用draw_text()进行渲染 + 每隔一行多一个行间距maxIOffset,这也就是maxIOffset乘i的原因,maxIOffset始终为32*/ + y = y + maxIOffset + 32; for (int i=0; i<this->m_linesOfCode.size(); ++i) { maxIOffset = (i * 32); draw_text(this->m_linesOfCode[i], COLOR_WHITE, "font_about", 18, x, y + (maxIOffset)); } - + //在描述的下面间隔32再次渲染linesOfCode的情况,也是每行间隔maxIOffset,32 y = y + maxIOffset + 64; draw_text("Contributions", COLOR_WHITE, "font_about", 18, x, y); y += 32; @@ -200,22 +227,23 @@ void AboutScreen::renderDescription() { maxIOffset = (i * 32); draw_text(this->m_gitContributions[i], COLOR_WHITE, "font_about", 18, x, y + (maxIOffset)); } - + //在linesOfCode的下面间隔32再次渲染Contributions的情况,也是每行间隔maxIOffset,32 } +//渲染描述,贡献者代码行数,贡献者情况 void AboutScreen::loop() { while (! this->m_shouldQuit) { - process_events(); + process_events();//调用splashkit.h里的事件处理函数 - this->readInput(); - this->tick(); - this->render(); + this->readInput();//检查有没有申请退出 + this->tick();//更新位置音乐计时器 + this->render();//渲染画面 - delay(1000 / 60); + delay(1000 / 60);//定下FPS为60 } } - +//循环函数 void AboutScreen::main() { // Clear music and start the about screen music. @@ -227,32 +255,35 @@ void AboutScreen::main() { // Tidy up once the loop is done. this->onExit(); } +//AboutScreen的入口函数 void AboutScreen::tickContributor() { this->m_contributorTicker++; if (this->m_contributorTicker >= CONTRIBUTION_TIME) { - this->m_contributorTicker = 0; - this->m_contributorsIndex = (this->m_contributorsIndex + 1) % this->m_contributors.size(); + this->m_contributorTicker = 0;//每过180帧重置,然后增加index换下一个人,在FPS为60的情况下它的时间为3s + this->m_contributorsIndex = (this->m_contributorsIndex + 1) % this->m_contributors.size();//取模防止越界且起到循环轮播效果,比如如果index现在是5,m_contributors size是4,那就取1轮播 } } +//实现循环轮播贡献者名字的支撑逻辑,一个3s void AboutScreen::renderContributor() { - int r = this->m_contributorTicker % CONTRIBUTION_TIME; - double ratio = 1; - double fontSize = 32; - double fontRatio = 1; + int r = this->m_contributorTicker % CONTRIBUTION_TIME;//r会始终保持在0-179的范围 + double ratio = 1;//字号透明度 + double fontSize = 32;//字号大小 + double fontRatio = 1;//字号倍率 if (r < CONTRIBUTION_FADE_TIME) { - ratio = r / (double)CONTRIBUTION_FADE_TIME; - fontRatio = 1 + ((1 - ratio) * 4); + ratio = r / (double)CONTRIBUTION_FADE_TIME;//随着r的增大,ratio会越来越大,也就是越来越亮直到1 + fontRatio = 1 + ((1 - ratio) * 4);//CONTRIBUTION_FADE_TIME是=30帧,也就是淡化阶段,时间为0.5s,随着ratio的增大,字体倍率会越来越小,也就是慢慢变小 + //实现淡入阶段效果,由大而淡变成小而亮 } else if ((CONTRIBUTION_TIME - r) < CONTRIBUTION_FADE_TIME) { - ratio = (CONTRIBUTION_TIME - r) / (double)CONTRIBUTION_FADE_TIME; - fontRatio = ratio; + ratio = (CONTRIBUTION_TIME - r) / (double)CONTRIBUTION_FADE_TIME;//随着r的增大,ratio会越来越小,也就是亮度越来越小 + fontRatio = ratio;//将字体倍率大小和亮度相等,实现淡出效果 } - fontSize = fontSize * fontRatio; + fontSize = fontSize * fontRatio;//实现字体大小和字体倍率之间的关系控制 if (ratio > 1.0) - ratio = 1.0; + ratio = 1.0;//限制ratio大小,防止出错 color c; c.r = ratio; @@ -266,15 +297,15 @@ void AboutScreen::renderContributor() { double y = 0; fontSize = 24; for (int i=0; i<createdBy.length(); ++i) { - y = sin((this->m_ticker + (i * 4)) / (double)16) * (double)9; - x = sin(this->m_ticker / (double) 50) * (double)225; - double actualX = 1300 + x + (i * 28); + y = sin((this->m_ticker + (i * 4)) / (double)16) * (double)9;//让每个字符Y位置不同,上下9个像素 + x = sin(this->m_ticker / (double) 50) * (double)225;//让每个字符X位置产生变化,左右225个像素 + double actualX = 1300 + x + (i * 28);//计算每个字符的实际位置,间隔28 - fontRatio = sin((actualX + 190) / (double)80) * (double)1.5; + fontRatio = sin((actualX + 190) / (double)80) * (double)1.5;//让每个字符大小不一样,1-1.5之间 if (fontRatio < 1) - fontRatio = (double)1; + fontRatio = (double)1;//限制fontRatio大小,防止太小 - color c = this->getRainbowShade(actualX - 350); - draw_text(createdBy.substr(i, 1), c, "font_about", (double)24 * fontRatio, actualX, (double)500 + y); + color c = this->getRainbowShade(actualX - 350);//将字符位置丢给彩虹函数生成彩虹效果 + draw_text(createdBy.substr(i, 1), c, "font_about", (double)24 * fontRatio, actualX, (double)500 + y);//绘制 } -} \ No newline at end of file +}//绘制贡献者,彩虹效果,淡入淡出,位置波动 \ No newline at end of file diff --git a/src/ArcadeMachine.cpp b/src/ArcadeMachine.cpp index 3ccb8df..906ae36 100644 --- a/src/ArcadeMachine.cpp +++ b/src/ArcadeMachine.cpp @@ -342,15 +342,55 @@ void ArcadeMachine::playArcadeTeamIntro() */ void ArcadeMachine::playSplashKitIntro() { - // Pull the most recent version of the arcade-games repo. - do + const int MAX_RETRIES = 3; + const int RETRY_DELAY_MS = 30000; + + for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { - // Draw SplashKit productions screen - this->m_introSplashkit.drawTitlePage(); - draw_text("Loading...", COLOR_SLATE_GRAY, "font_text", 60, ARCADE_MACHINE_RES_X / 2 - 100, ARCADE_MACHINE_RES_Y / 2 + 350); + // Show attempt status on screen and terminal + std::string attemptMsg = "Connecting... (attempt " + std::to_string(attempt) + "/" + std::to_string(MAX_RETRIES) + ")"; + std::cerr << "[Git] " << attemptMsg << std::endl; + + process_events(); + clear_screen(); + m_introSplashkit.drawTitlePage(); + draw_text(attemptMsg, COLOR_SLATE_GRAY, "font_text", 40, ARCADE_MACHINE_RES_X / 2 - 220, ARCADE_MACHINE_RES_Y / 2 + 350); refresh_screen(); - - } while (!this->m_config.getFromGit("https://github.com/thoth-tech/arcade-games.git", "games")); + + if (this->m_config.getFromGit("https://github.com/thoth-tech/arcade-games.git", "games")) + { + std::cerr << "[Git] games loaded successfully on attempt " << attempt << std::endl; + return; + } + + // This attempt failed + if (attempt < MAX_RETRIES) + { + std::string retryMsg = "Failed. Retrying in 30 seconds... (" + std::to_string(attempt) + "/" + std::to_string(MAX_RETRIES) + ")"; + std::cerr << "[Git] attempt " << attempt << " failed. Retrying in 30 seconds..." << std::endl; + + process_events(); + clear_screen(); + m_introSplashkit.drawTitlePage(); + draw_text(retryMsg, COLOR_RED, "font_text", 40, ARCADE_MACHINE_RES_X / 2 - 300, ARCADE_MACHINE_RES_Y / 2 + 350); + refresh_screen(); + delay(RETRY_DELAY_MS); + } + } + + // All retries failed — show error on screen and terminal, then exit + std::string failMsg1 = "Failed to load games after " + std::to_string(MAX_RETRIES) + " attempts."; + std::string failMsg2 = "Check network connection and restart."; + std::cerr << "[Git] " << failMsg1 << " Exiting." << std::endl; + + process_events(); + clear_screen(); + m_introSplashkit.drawTitlePage(); + draw_text(failMsg1, COLOR_RED, "font_text", 40, ARCADE_MACHINE_RES_X / 2 - 300, ARCADE_MACHINE_RES_Y / 2 + 320); + draw_text(failMsg2, COLOR_RED, "font_text", 40, ARCADE_MACHINE_RES_X / 2 - 280, ARCADE_MACHINE_RES_Y / 2 + 370); + refresh_screen(); + delay(5000); + exit(EXIT_FAILURE); } /** diff --git a/src/Button.cpp b/src/Button.cpp index 948c0c4..85dfa58 100644 --- a/src/Button.cpp +++ b/src/Button.cpp @@ -52,7 +52,7 @@ Button::Button(Color c, float x, float y, int xCell, int yCell, float scale) sprite_set_x(this->m_btn, this->m_x - this->m_centreX); sprite_set_y(this->m_btn, this->m_y - this->m_centreY); // store the window centre point of button as location - this->m_btnLocation = center_point(this->m_btn); + this->m_btnLocation = point_at(this->m_x, this->m_y); // scale the sprite sprite_set_scale(this->m_btn, scale); } diff --git a/src/ConfigData.cpp b/src/ConfigData.cpp index 776ef46..917d882 100644 --- a/src/ConfigData.cpp +++ b/src/ConfigData.cpp @@ -7,8 +7,14 @@ #include <sys/types.h> #include <exception> +#if __cplusplus >= 201703L +#include <filesystem> +namespace fs = std::filesystem; +#else +#define _LIBCPP_NO_EXPERIMENTAL_DEPRECATION_WARNING_FILESYSTEM #include <experimental/filesystem> namespace fs = std::experimental::filesystem; +#endif /** * @brief Construct a new Config Data object @@ -18,6 +24,48 @@ namespace fs = std::experimental::filesystem; ConfigData::ConfigData(std::string configFile) { collectConfigData(readTxt(openFile(configFile))); + + if (this->m_title.empty()) + std::cerr << "[Config] missing field 'title' in: " << configFile << std::endl; + if (this->m_image.empty()) + std::cerr << "[Config] missing field 'image' in: " << configFile << std::endl; +} + +void ConfigData::setFolder(std::string &dir) +{ + m_folder = dir; + + fs::path buildsPath = fs::path(dir) / "builds"; + + if (!fs::exists(buildsPath) || !fs::is_directory(buildsPath)) + { + std::cerr << "[Config] no builds/ directory: " << dir << std::endl; + return; + } + + bool hasLinuxArm = false, hasLinuxX86 = false, hasWindows = false; + + for (const auto &entry : fs::directory_iterator(buildsPath)) + { + std::string name = entry.path().filename().string(); + if (name == "linux-arm.out") hasLinuxArm = true; + if (name == "linux-x86.out") hasLinuxX86 = true; + if (name == "windows-x86.exe") hasWindows = true; + } + + if (!hasLinuxArm && !hasLinuxX86 && !hasWindows) + { + std::cerr << "[Config] builds/ is empty: " << dir << std::endl; + return; + } + + std::string platforms = ""; + if (hasLinuxArm) platforms += "Linux ARM, "; + if (hasLinuxX86) platforms += "Linux x86, "; + if (hasWindows) platforms += "Windows, "; + platforms = platforms.substr(0, platforms.size() - 2); // trim trailing ", " + + std::cerr << "[Config] " << m_title << " supports: " << platforms << std::endl; } /** @@ -34,7 +82,7 @@ std::ifstream ConfigData::openFile(std::string file) if(configFile.fail()) { - std::cerr << "Error Opening File" << std::endl; + std::cerr << "[Config] failed to open file: " << file << std::endl; exit(1); } @@ -141,23 +189,29 @@ bool ConfigData::getFromGit(std::string url, const char* dir) { // info struct lets us query the directory to see if it exists struct stat info; - if (stat(dir, &info) != 0) - { - // cant access dir -- clone from scratch - system(("git clone " + url + " " + dir).c_str()); - } - else if (info.st_mode &S_IFDIR) + bool isPull = (stat(dir, &info) == 0 && (info.st_mode & S_IFDIR)); + + if (isPull) { - // dir exists -- pull instead + std::cerr << "[Git] pulling: " << url << " → " << dir << std::endl; system(("git -C " + std::string(dir) + " pull " + url).c_str()); } else { - // dir does not exist -- clone from scratch + std::cerr << "[Git] cloning: " << url << " → " << dir << std::endl; system(("git clone " + url + " " + dir).c_str()); } - return true; + // Verify the directory actually exists after the git operation + struct stat checkInfo; + bool success = (stat(dir, &checkInfo) == 0 && (checkInfo.st_mode & S_IFDIR)); + + if (success) + std::cerr << "[Git] " << (isPull ? "pull" : "clone") << " succeeded: " << dir << std::endl; + else + std::cerr << "[Git] " << (isPull ? "pull" : "clone") << " failed: " << dir << std::endl; + + return success; } /** diff --git a/src/Menu.cpp b/src/Menu.cpp index 84423dd..3ce2b28 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -6,9 +6,19 @@ #endif #include <cmath> +#include <iostream> + +#if __cplusplus >= 201703L +#include <filesystem> +namespace fs = std::filesystem; +#else +#include <experimental/filesystem> +namespace fs = std::experimental::filesystem; +#endif Menu::Menu() { + this->m_tip = nullptr; this->m_db = new Database(); Table *gameDataTable = new Table("gameData", {{"gameName", "TEXT"}, {"startTime", "TEXT"}, {"endTime", "TEXT"}, {"rating", "TEXT"}, {"highScore", "TEXT"}}); this->m_db->createTable(gameDataTable); @@ -17,6 +27,7 @@ Menu::Menu() Menu::Menu(std::vector<ConfigData> configs) { + this->m_tip = nullptr; this->m_games = configs; this->m_db = new Database(); Table *gameDataTable = new Table("gameData", {{"gameName", "TEXT"}, {"startTime", "TEXT"}, {"endTime", "TEXT"}, {"rating", "TEXT"}, {"highScore", "TEXT"}}); @@ -31,6 +42,24 @@ Menu::~Menu() { std::cout << "Destructor called on Menu\n"; std::cout << "Menu: clearing memory...\n"; + + // Walk the circular doubly-linked list and free every ButtonNode and its Button. + // getButtons() always returned an empty m_btns, so ArcadeMachine's destructor + // never freed these — they must be freed here. + if (m_button) { + ButtonNode *current = m_button->getNext(); + while (current != m_button) { + ButtonNode *next = current->getNext(); + delete current->button; + delete current; + current = next; + } + // Delete the head node last (after all others are gone) + delete m_button->button; + delete m_button; + m_button = nullptr; + } + delete m_db; delete m_tip; } @@ -80,15 +109,30 @@ void Menu::createButtons() for (int i = 0; i < m_gameImages.size(); i++) { + std::string image = m_gameImages[i]; + + // Load the game thumbnail bitmap with specific error reporting. + if (m_games[i].image().empty()) { + std::cerr << "[Menu] image load failed (empty path): " << m_games[i].title() << std::endl; + } else if (!fs::exists(image)) { + std::cerr << "[Menu] image load failed (file not found): " << image << std::endl; + } else { + if (!has_bitmap(image)) + load_bitmap(image, image); + if (!has_bitmap(image)) + std::cerr << "[Menu] image load failed (splashkit error): " << image << std::endl; + else + std::cerr << "[Menu] image loaded: " << image << std::endl; + } + if (i == 0) { - this->m_button = new ButtonNode(new GameScreenButton(Button::GAME, m_gameImages[0])); + this->m_button = new ButtonNode(new GameScreenButton(Button::GAME, image)); this->m_button->config = m_games[0]; this->m_button->stats = gameStats.getStats(this->m_db, m_games[0].title()); } else { - std::string image = m_gameImages[i]; this->m_button->addBefore(new ButtonNode(new GameScreenButton(Button::GAME, image))); this->m_button->getPrev()->config = m_games[i]; this->m_button->getPrev()->stats = gameStats.getStats(this->m_db, m_games[i].title()); @@ -146,6 +190,15 @@ void Menu::carouselHandler() checkGameExit(); + // ESC while in game: kill the game process + // Use key_typed() directly since game window may have stolen focus from selector + if (m_inGame && key_typed(ESCAPE_KEY)) { + std::cerr << "[Menu] user pressed ESC, killing game (PID: " << this->m_processId << ")" << std::endl; + killProcess(this->m_processId); + m_launching = false; + m_inGame = false; + } + if (this->m_button) { if (this->m_action == "escape" && m_overlayActive) @@ -156,16 +209,6 @@ void Menu::carouselHandler() { if (m_overlayActive) { - -#ifdef _WIN32 - // Get game path - m_gamePath = (this->m_button->config.folder() + "/" + this->m_button->config.win_exe()).c_str(); - // Get executable name - m_gameExe = strdup(this->m_button->config.win_exe().c_str()); - // Get game directory - m_gameDir = this->m_button->config.folder().c_str(); -#endif - // Set the center of the game this->m_x = m_centreX; this->m_y = m_centreY; @@ -189,15 +232,19 @@ void Menu::carouselHandler() m_gameData.setGameName(this->m_button->config.title()); #ifdef _WIN32 - // Get game path - m_gamePath = (this->m_button->config.folder() + "/" + this->m_button->config.win_exe()).c_str(); - // Get executable name - m_gameExe = strdup(this->m_button->config.win_exe().c_str()); - // Get game directory - m_gameDir = this->m_button->config.folder().c_str(); - - // Call method to open game executable - startGame(m_gamePath, m_gameExe, m_gameDir); + { + // Build persistent strings before passing to CreateProcessA. + // Storing .c_str() of a temporary causes a dangling pointer — + // the temporary is destroyed at the end of the assignment statement. + std::string gamePath = this->m_button->config.folder() + "/" + this->m_button->config.win_exe(); + std::string gameDir = this->m_button->config.folder(); + // CreateProcessA may write into lpCommandLine, so use a mutable buffer. + std::string gameExeStr = this->m_button->config.win_exe(); + std::vector<char> gameExeBuf(gameExeStr.begin(), gameExeStr.end()); + gameExeBuf.push_back('\0'); + + startGame(gamePath.c_str(), gameExeBuf.data(), gameDir.c_str()); + } #else try { startGame(this->m_button->config.getExecutablePath()); @@ -238,9 +285,27 @@ void Menu::drawMenuPage() if (m_overlayActive && !m_menuSliding) drawOverlay(m_button->config, m_button->stats); - if (!m_inGame) + if (!m_inGame && this->m_tip) this->m_tip->draw(); + // Show launching animation until game process exits. + if (m_inGame && m_launching) { + int dots = (current_ticks() / 400) % 4; + std::string anim = std::string(dots, '.'); + std::string msg = "Starting " + this->m_button->config.title() + anim; + draw_text(msg, COLOR_WHITE, "font_text", 40, + m_centreX - 200, m_centreY - 20); + } + + // Show launch error message (fork failure or crash). + if (!m_inGame && !m_launchError.empty()) { + draw_text(m_launchError, COLOR_RED, "font_text", 30, + m_centreX - 300, m_centreY - 20); + // Clear after 3 seconds so the menu returns to normal. + if (current_ticks() - m_launchTime > 3000) + m_launchError = ""; + } + updateCarousel(); carouselHandler(); } @@ -452,8 +517,22 @@ void Menu::startGame(LPCSTR gamePath,LPSTR gameExe, LPCSTR gameDirectory) #else void Menu::startGame(struct s_ExecutablePath path) { if (!this->m_inGame) { + std::cerr << "[Menu] starting game: " << path.file << " from " << path.path << std::endl; + m_launching = true; + m_launchError = ""; + this->m_processId = spawnProcess(path.path, path.file); - this->m_inGame = true; + + if (this->m_processId == -1) { + std::cerr << "[Menu] failed to start game: fork failed" << std::endl; + m_launching = false; + m_launchTime = current_ticks(); + m_launchError = "Failed to start game — see terminal for details"; + } else { + std::cerr << "[Menu] game process spawned (PID: " << this->m_processId << ")" << std::endl; + this->m_inGame = true; + this->m_launchTime = current_ticks(); + } } } #endif @@ -481,7 +560,19 @@ void Menu::checkGameExit() m_gameData.setHighScore(highScore); } #else - if (! processRunning(this->m_processId)) { + if (! processRunning(this->m_processId, m_lastExitCode)) { + unsigned int runTime = current_ticks() - m_launchTime; + if (m_lastExitCode != 0) { + std::cerr << "[Menu] game crashed: " << this->m_button->config.title() + << " (PID: " << this->m_processId << ", exit code: " << m_lastExitCode + << ", ran for " << runTime << "ms)" << std::endl; + m_launchError = this->m_button->config.title() + " crashed (exit code: " + std::to_string(m_lastExitCode) + ")"; + m_launchTime = current_ticks(); + } else { + std::cerr << "[Menu] game exited normally: " << this->m_button->config.title() + << " (PID: " << this->m_processId << ", ran for " << runTime << "ms)" << std::endl; + } + m_launching = false; this->m_inGame = false; int highScore = 0; m_gameData.setEndTime(time(0)); diff --git a/src/Option.cpp b/src/Option.cpp index 9c284d3..708405e 100644 --- a/src/Option.cpp +++ b/src/Option.cpp @@ -2,6 +2,25 @@ Option::Option() {} +Option::~Option() +{ + // Free ButtonNode objects only — Button pointers are owned by m_optionsBtns, + // so do NOT delete button inside each node here (would double-free). + if (m_optionsButtonNode) { + ButtonNode *current = m_optionsButtonNode->getNext(); + while (current != m_optionsButtonNode) { + ButtonNode *next = current->getNext(); + delete current; + current = next; + } + delete m_optionsButtonNode; + m_optionsButtonNode = nullptr; + } + // Free Button objects via m_optionsBtns (the single owner) + for (auto& btn : m_optionsBtns) delete btn; + m_optionsBtns.clear(); +} + void Option::createOptionsButtons() { // Initialise grid @@ -70,7 +89,7 @@ void Option::drawOptionsMenu() this->m_optionsButtonNode = this->m_selectorOptionsMenu.checkKeyInput(this->m_optionsButtonNode); int x_pos = home.button->x() + (sprite_width(home.button->btn()) - 40); - int y_pos; + int y_pos = home.button->y() + ((sprite_width(home.button->btn())*0.5)-30); // default to home position if (this->m_optionsButtonNode->button->color() == "opts_home") y_pos = home.button->y() + ((sprite_width(home.button->btn())*0.5)-30); else if (this->m_optionsButtonNode->button->color() == "opts_sound") y_pos = sound.button->y() + ((sprite_width(sound.button->btn())*0.5)-30); diff --git a/src/Process.cpp b/src/Process.cpp index b536550..5df91f5 100644 --- a/src/Process.cpp +++ b/src/Process.cpp @@ -4,7 +4,11 @@ #include <iostream> #include <array> #include <sys/types.h> +#include <sys/stat.h> #include <signal.h> +#include <sys/wait.h> +#include <cerrno> +#include <cstring> pid_t spawnProcess(std::string directory, std::string fileName) { @@ -13,20 +17,36 @@ pid_t spawnProcess(std::string directory, std::string fileName) { // First, fork the current process into a new process. // This is required to ensure that process execution occurs concurrently. pid_t processId = fork(); - if (processId > 0) + + if (processId == -1) { + std::cerr << "[Process] fork failed: " << strerror(errno) << std::endl; + return -1; + } + + if (processId > 0) { + std::cerr << "[Process] spawned game: " << directory << "/" << fileName << " (PID: " << processId << ")" << std::endl; return processId; + } + + // Create new process group so we can kill the entire game tree later + setpgid(0, 0); std::array<char, 128> buffer; // The working directory must be changed to the root directory of the game - // to ensure that SplashKit resources are correctly pathed when the process + // to ensure that SplashKit resources are correctly pathed when the process // executes. - chdir(directory.c_str()); + if (chdir(directory.c_str()) != 0) { + std::cerr << "[Process] chdir failed: " << directory << std::endl; + exit(EXIT_FAILURE); + } - std::string cmd = "./builds/" + fileName; + std::string exePath = "./builds/" + fileName; + chmod(exePath.c_str(), 0755); + std::string cmd = exePath + " 2>&1"; auto pipe = popen(cmd.c_str(), "r"); if (! pipe) { - std::cerr << "Error executing popen" << std::endl; + std::cerr << "[Process] popen failed: " << cmd << std::endl; exit(EXIT_FAILURE); } @@ -34,12 +54,13 @@ pid_t spawnProcess(std::string directory, std::string fileName) { // streams. This could be extended by piping them into shared // memory for consumption by the main process. while (fgets(buffer.data(), 128, pipe) != NULL) { - ; + std::cerr << "[Game] " << buffer.data(); } - pclose(pipe); + int ret = pclose(pipe); + int gameExitCode = WIFEXITED(ret) ? WEXITSTATUS(ret) : 1; - exit(EXIT_SUCCESS); + exit(gameExitCode); #endif @@ -48,13 +69,32 @@ pid_t spawnProcess(std::string directory, std::string fileName) { } -bool processRunning(pid_t processId) { +bool processRunning(pid_t processId, int &exitCode) { #ifndef _WIN32 - return getpgid(processId) >= 0; + int status; + pid_t result = waitpid(processId, &status, WNOHANG); + if (result == 0) return true; // still running + if (result == processId) { + if (WIFEXITED(status)) + exitCode = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + exitCode = 128 + WTERMSIG(status); // signal = non-zero = crash + else + exitCode = -1; + } + return false; #endif // TODO: Add Windows support. + exitCode = 0; return false; -} \ No newline at end of file +} + +void killProcess(pid_t processId) { +#ifndef _WIN32 + std::cerr << "[Process] killing game process group (PID: " << processId << ")" << std::endl; + kill(-processId, SIGTERM); +#endif +} diff --git a/src/program.cpp b/src/program.cpp index 8dac613..9c2ebb0 100644 --- a/src/program.cpp +++ b/src/program.cpp @@ -1,5 +1,15 @@ #include "Configuration.h" #include "ArcadeMachine.h" +#include <iostream> + +#if __cplusplus >= 201703L +#include <filesystem> +namespace fs = std::filesystem; +#else +#include <experimental/filesystem> +namespace fs = std::experimental::filesystem; +#endif + #define PLAY_INTRO true #define LOAD_GAMES true @@ -7,15 +17,58 @@ int main(void) { // Load all resources set_resources_path("resources" ARCADE_MACHINE_PATH_SEP); + + if (!fs::exists("resources/bundles/resources.txt")) + std::cerr << "[Bundle] resources.txt not found — check resources/ directory" << std::endl; + load_resource_bundle("bundle", "resources.txt"); - // Instantiate Arcade Machine - ArcadeMachine Arcade; + if (has_resource_bundle("bundle")) + std::cerr << "[Bundle] resource bundle loaded successfully" << std::endl; + else + std::cerr << "[Bundle] resource bundle failed to load (unknown error)" << std::endl; // Open window and toggle border off. + // Note: bitmap/font checks must happen AFTER open_window() since SplashKit + // needs a render context to load graphical resources. open_window("arcade-machine", ARCADE_MACHINE_RES_X, ARCADE_MACHINE_RES_Y); window_toggle_border("arcade-machine"); + // Instantiate Arcade Machine + ArcadeMachine Arcade; + + // Check all critical bitmaps and fonts loaded correctly. + // Must happen after open_window() — SplashKit needs a render context. + std::vector<std::string> bitmaps = { + "thoth", "intro_splashkit", "intro_thoth_tech", "intro_arcade_team", + "games_dashboard", "back_ground", "in_game_bgnd", "rating_bg", "options_thoth", + "btn_play", "btn_exit", "btn_opts", "play_hghlt", "exit_hghlt", "options_hghlt", + "game_hghlt", "opts_home", "opts_sound", "opts_display", "opts_stats", + "opts_home_hghlt", "opts_sound_hghlt", "opts_display_hghlt", "opts_stats_hghlt", + "backCurrentGame", "backGame_notSelected", "changeSound", "sound_notSelected", + "backMenu_notSelected", "backMenu", "cursor", "information", "star-gold", "star-black" + }; + std::vector<std::string> fonts = { + "font_btn", "font_title", "font_text", "font_star", "font_about" + }; + int failCount = 0; + for (const auto& name : bitmaps) { + if (!has_bitmap(name)) { + std::cerr << "[Bundle] bitmap not loaded: " << name << std::endl; + failCount++; + } + } + for (const auto& name : fonts) { + if (!has_font(name)) { + std::cerr << "[Bundle] font not loaded: " << name << std::endl; + failCount++; + } + } + if (failCount == 0) + std::cerr << "[Bundle] all bitmaps and fonts loaded successfully" << std::endl; + else + std::cerr << "[Bundle] " << failCount << " resource(s) failed to load" << std::endl; + #if PLAY_INTRO == true // Play Thoth Tech intro Arcade.playThothTechIntro(); From 8bd6862807ec6c9d829e4d2477cc4537f3f88db3 Mon Sep 17 00:00:00 2001 From: seventhback777 <903157209@qq.com> Date: Thu, 2 Apr 2026 01:17:40 +1100 Subject: [PATCH 5/7] update --- HANDOFF.md | 215 ----------------------------------------------------- 1 file changed, 215 deletions(-) delete mode 100644 HANDOFF.md diff --git a/HANDOFF.md b/HANDOFF.md deleted file mode 100644 index e937491..0000000 --- a/HANDOFF.md +++ /dev/null @@ -1,215 +0,0 @@ -# Arcade Machine — Engineering Handoff Document - -> Last updated: 2026-04-01 (observability work in progress) -> Original session: Claude Sonnet 4.6 via VS Code extension -> Recipient: Next AI assistant (ChatGPT Codex or new Claude session) - ---- - -## Project Overview - -C++ arcade machine frontend built on the **SplashKit** game framework (SDL2-based). -Runs on a **Raspberry Pi 3B** (Linux ARM), displays a carousel game menu on a TV, -launches games as child processes, tracks stats via SQLite. - -**Working directory on RPi:** `~/arcade-machine/` -**Games directory:** `~/arcade-machine/games/games/` -**Connection:** SSH from developer laptop - ---- - -## Architecture Summary - -``` -program.cpp - └── ArcadeMachine - ├── Menu — carousel UI, game launching (src/Menu.cpp) - │ └── ButtonNode — circular doubly-linked list of game buttons - ├── Option — settings screen (src/Option.cpp) - ├── AboutScreen — about/credits screen - └── Rating — star rating system - -Game discovery chain: - Helper::ConfigDataList() - → getConfigFiles("./games/games") [scans for config.txt files] - → ConfigData(configFile) [parses each config.txt] - → Menu::createButtons() [loads images, creates sprites] - -Game launch chain (Linux): - Menu::carouselHandler() - → ConfigData::getExecutablePath() [finds binary, two paths — see below] - → Menu::startGame(path) - → spawnProcess(dir, file) [fork() + popen()] - → Menu::checkGameExit() [polls processRunning() each frame] -``` - -### Key files - -| File | Role | -|------|------| -| `src/program.cpp` | Entry point | -| `src/ArcadeMachine.cpp` | Top-level controller | -| `src/Menu.cpp` | Carousel menu + game launching | -| `src/Process.cpp` | Linux fork/popen process management | -| `src/ConfigData.cpp` | config.txt parser + git clone/pull | -| `src/Option.cpp` | Options/settings screen | -| `include/Helper.h` | Game discovery, filesystem scanning | -| `include/Rating.h` | Star rating value object | -| `resources/bundles/resources.txt` | SplashKit resource bundle manifest | - -### Platform macros (defined in Configuration.h) - -- `ARCADE_MACHINE_OS` — `"linux"` / `"windows"` / `"macos"` -- `ARCADE_MACHINE_INSTRUCTION_SET` — `"arm"` / `"aarch64"` / `"x86_64"` -- `ARCADE_MACHINE_BINARY_EXT` — `""` (Linux/Mac) / `".exe"` (Windows) -- `ARCADE_MACHINE_PATH_SEP` — `"/"` (Linux/Mac) / `"\\"` (Windows) - ---- - -## Game Executable Resolution (`ConfigData::getExecutablePath`) - -Two-path fallback logic in `src/ConfigData.cpp:233`: - -**Path 1 — Auto build (preferred):** -Looks for `{game_folder}/builds/linux-arm` (or `linux-aarch64`, `windows-x86_64.exe`, etc.) -No config.txt changes needed; file must exist with correct naming convention. - -**Path 2 — Manual config (fallback):** -Reads `lin-exe=` field from `config.txt`. -Throws `std::runtime_error` if empty or file not found. -Exception IS caught in `Menu.cpp` and printed via `write_line()`. - -**Current state:** All 9 games have empty `Linux Bin` — neither path works for any game. -This is a content problem (games not compiled for Linux/ARM), not a code bug. - ---- - -## Current Task Priority - -### Priority 1 — Observability (IN PROGRESS) - -All output uses `std::cerr << "[Module] message" << std::endl` format. -`std::endl` flushes immediately — safe even if program crashes mid-run. -Visible in SSH terminal when running `DISPLAY=:0 ./ArcadeMachine`. - -#### DONE - -**`src/Process.cpp`** — fully instrumented: -- `fork()` returns -1 → `[Process] fork failed: <strerror>` -- Parent after successful fork → `[Process] spawned game: <dir>/<file> (PID: <pid>)` -- `chdir()` fails → `[Process] chdir failed: <directory>` then exits child -- `popen()` fails → `[Process] popen failed: <cmd>` then exits child - -**`src/Menu.cpp` — `createButtons()`** — image loading instrumented: -- `image=` field empty in config.txt → `[Menu] image load failed (empty path): <title>` -- File not found on disk → `[Menu] image load failed (file not found): <path>` -- SplashKit failed to load → `[Menu] image load failed (splashkit error): <path>` -- Success → `[Menu] image loaded: <path>` - -**`src/Menu.cpp` — `startGame()` + `drawMenuPage()` + `checkGameExit()`** — game launch全流程: -- 启动前 → `[Menu] starting game: <file> from <path>` -- fork失败 → `[Menu] failed to start game: fork failed` + 屏幕红色错误3秒 -- fork成功 → `[Menu] game process spawned (PID: <pid>)` + 屏幕动态"Starting <title>..." -- 游戏3秒内退出 → `[Menu] game crashed on startup` + 屏幕红色崩溃信息3秒 -- 正常退出 → `[Menu] game process ended: <title> (PID: <pid>)` - -**`src/ConfigData.cpp` — `getFromGit()`** — git 操作全程有输出: -- 开始前 → `[Git] cloning/pulling: <url> → <dir>` -- 结束后 → `[Git] clone/pull succeeded/failed: <dir>` - -**`include/Helper.h` — `getConfigFiles()`** — 扫描结果: -- 扫描结束 → `[Helper] found N config files in <dir>` - -**`src/ConfigData.cpp` — `ConfigData` 构造函数** — 关键字段缺失检查: -- `title` 为空 → `[Config] missing title: <config_path>` -- `image` 为空 → `[Config] missing image: <config_path>` -- `linux-bin` 为空 → `[Config] missing linux-bin: <config_path>` - -**`src/program.cpp`** — 启动资源检查: -- `resources.txt` 不存在 → `[Bundle] resources.txt not found` -- 资源包加载结果 → `[Bundle] resource bundle loaded/failed` -- 逐个检查所有 bitmap 和 font → `[Bundle] bitmap not loaded: <name>` -- 汇总 → `[Bundle] N resource(s) failed to load` - -**`src/ArcadeMachine.cpp` — `playSplashKitIntro()`** — git 失败不再死循环: -- 最多重试3次,每次等待30秒 -- 每次在屏幕和终端输出当前是第几次尝试 -- 3次全失败 → 屏幕显示错误5秒后 `exit(EXIT_FAILURE)` - -**`src/ConfigData.cpp` — `openFile()`** — 报错补充文件路径: -- `[Config] failed to open file: <path>` - -#### PENDING - -无。Priority 1 可观测性工作全部完成。 - -### Priority 2 — Bug fixes (DONE — 16 bugs fixed, not yet deployed to RPi) -See section below. - -### Priority 3 — Game loading -Games need compiled Linux ARM binaries. Either: -- Pre-compile and place in `{game_folder}/builds/linux-arm` (or `linux-aarch64`) -- Or add `lin-exe=` to each game's `config.txt` - -Some games reportedly have pre-built versions; needs investigation of actual directory structure. - ---- - -## Completed Bug Fixes (not yet synced to RPi) - -All fixes are in the working directory on the developer's laptop. -**None of these have been deployed to the RPi yet.** - -| # | File | Problem | Fix | -|---|------|---------|-----| -| 1 | `include/Helper.h` | `getFolderName()` returned trailing slash → double-slash paths | Strip trailing `/` or `\` | -| 2 | `include/Helper.h` | `getConfigFiles()` no existence check → crash if `./games/games` missing | Add `fs::exists()` guard | -| 3 | `src/ConfigData.cpp` | Hardcoded `#include <experimental/filesystem>` → compile failure on C++17 | Conditional include | -| 4 | `src/ConfigData.cpp` | `getFromGit()` always returned `true` even on git failure | Check directory exists after clone | -| 5 | `src/Menu.cpp` | `createButtons()` called `create_sprite` without prior `load_bitmap` | Add `load_bitmap(image, image)` before `create_sprite` | -| 6 | `src/Menu.cpp` | Windows launch: dangling pointer from `.c_str()` on temporary string | Use persistent `std::string` + `std::vector<char>` | -| 7 | `src/Menu.cpp` | Windows launch: duplicate pre-fade variable assignment | Remove duplicate line | -| 8 | `src/Menu.cpp` | Both constructors: `m_tip` uninitialized | Add `m_tip = nullptr` | -| 9 | `src/Menu.cpp` | `drawMenuPage()`: unconditional `m_tip->draw()` → null dereference | Guard with `if (!m_inGame && m_tip)` | -| 10 | `src/Menu.cpp` | Destructor: circular `ButtonNode` list never freed → memory leak | Add full list traversal and delete | -| 11 | `include/Menu.h` | Dead `LPCSTR m_gamePath`, `LPSTR m_gameExe`, `LPCSTR m_gameDir` members | Remove | -| 12 | `src/Option.cpp` | `y_pos` uninitialized → undefined behaviour | Add `= 0` | -| 13 | `src/Option.cpp` | Missing destructor → `ButtonNode` list leak | Add `~Option()` with list cleanup | -| 14 | `include/Option.h` | Circular `#include "ArcadeMachine.h"` (unused) | Remove | -| 15 | `src/program.cpp` | `ArcadeMachine` constructed before `open_window()` → `create_sprite` called without render context | Move `open_window` + `window_toggle_border` before constructor | -| 16 | `resources/bundles/resources.txt` | 4 music entries had leading spaces in names → SplashKit lookup fails | Remove spaces: `MUSIC, 1, 1.mp3` → `MUSIC,1,1.mp3` | - ---- - -## Known Issues / Not Yet Fixed - -### All games — No Linux executables configured -All 9 games have empty `Linux Bin` in their config.txt. -`getExecutablePath()` throws `std::runtime_error` which IS caught and printed via `write_line()`. -Games with pre-built binaries need path configured or binaries moved to `builds/linux-arm` (or `linux-aarch64`). - ---- - -## Deployment Notes - -Changes are on developer laptop only. To deploy to RPi: -```bash -# Option A: rsync -rsync -av --exclude='.git' ~/path/to/arcade-machine/ pi@192.168.x.x:~/arcade-machine/ - -# Option B: git commit + push + pull on RPi -``` -RPi is accessed via SSH. Program launched with: -```bash -DISPLAY=:0 ./ArcadeMachine -``` - ---- - -## Collaboration Style Notes - -- User has C++ basics, not deeply familiar with this codebase -- Teach concepts as we go, don't just silently fix everything -- One-shot fixes preferred — find ALL issues in a file before reporting -- RPi is physical hardware; can't iterate quickly → get fixes right before deploying -- Current session partner: Claude Sonnet 4.6 (VS Code extension) From 557dbb0afc4f4d78954aac9c0a995a681394a5a4 Mon Sep 17 00:00:00 2001 From: seventhback777 <903157209@qq.com> Date: Thu, 2 Apr 2026 02:34:19 +1100 Subject: [PATCH 6/7] fix Database.h: use stubs on all platforms --- include/Database.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Database.h b/include/Database.h index be3b2db..e887e84 100644 --- a/include/Database.h +++ b/include/Database.h @@ -15,7 +15,7 @@ // SplashKit provides the real API so no stubs are needed. // TODO: Rewrite Database.h using raw SQLite3 (-lsqlite3) to restore functionality. // --------------------------------------------------------------------------- -#ifdef _WIN32 +#if 1 struct database {}; struct query_result {}; inline database open_database(std::string, std::string) { return {}; } From babfe2a6dfd2450d2c5a8e7f76246167225fe60c Mon Sep 17 00:00:00 2001 From: seventhback777 <903157209@qq.com> Date: Thu, 2 Apr 2026 02:49:06 +1100 Subject: [PATCH 7/7] fix: add missing cstring include for strcpy --- src/ConfigData.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ConfigData.cpp b/src/ConfigData.cpp index 917d882..d166b68 100644 --- a/src/ConfigData.cpp +++ b/src/ConfigData.cpp @@ -5,6 +5,7 @@ #include <iostream> #include <sys/stat.h> #include <sys/types.h> +#include <cstring> #include <exception> #if __cplusplus >= 201703L