Compare commits
	
		
			570 Commits
		
	
	
		
			revert-71a
			...
			b680d7bc58
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | b680d7bc58 | ||
|  | 30559bf443 | ||
|  | 8774c4246d | ||
|  | 7b82b20736 | ||
|  | 698937e77c | ||
|  | 7cbf000c44 | ||
|  | e98e17f6d7 | ||
|  | e337303408 | ||
|  | 97619fe662 | ||
|  | 246566a211 | ||
|  | 96a78ee61b | ||
|  | c9e99ee9ff | ||
|  | 111f2b1283 | ||
|  | 93386fa77a | ||
|  | 6cdf0eee53 | ||
|  | b6803c562c | ||
|  | 38699ac07a | ||
|  | f396ef52f2 | ||
|  | 73bf147817 | ||
|  | 80f9054add | ||
|  | b819e1ca9b | ||
|  | c4955e0b57 | ||
|  | 008cb1ebe4 | ||
|  | 723c934d73 | ||
|  | 4eac2e3312 | ||
|  | fef881f42c | ||
|  | eeaf29a646 | ||
|  | 2eae0fce2a | ||
|  | aa9e8ed3f0 | ||
|  | 3e14a2674f | ||
|  | 815899fa8d | ||
|  | 793e93ef24 | ||
|  | 43d0549e9e | ||
|  | 170afe2718 | ||
|  | 17220c228b | ||
|  | 0f1be83064 | ||
|  | e0ab09295d | ||
|  | 758e9b8397 | ||
|  | b3fe289fbe | ||
|  | 42fefb2899 | ||
|  | 5da86117c8 | ||
|  | 1376da3ba6 | ||
|  | fb280101a5 | ||
|  | 844495ebbb | ||
|  | e14f6bbef7 | ||
|  | 4839ddd497 | ||
|  | b901323e2f | ||
|  | 640665fd29 | ||
|  | ba8791d9fe | ||
|  | f8fe0b9877 | ||
|  | 48913356e3 | ||
|  | e498c6f179 | ||
|  | 31fe60807f | ||
|  | c758ba9735 | ||
|  | 650dcb85db | ||
|  | 6b110c81c8 | ||
|  | 93ef00870b | ||
|  | 645433610f | ||
|  | 10a5b6b4d4 | ||
|  | 69fccefed9 | ||
|  | 14aecdd929 | ||
|  | 48f18dd468 | ||
|  | 96fe09c6fa | ||
|  | 14c68f55d9 | ||
|  | 392a81b2d8 | ||
|  | 944b6b674e | ||
|  | 5f4c6be7b8 | ||
|  | aca92cd7f1 | ||
|  | b50b5e64d1 | ||
|  | 551786bf30 | ||
|  | 2aca94100e | ||
|  | 9e81fb8549 | ||
|  | 3c6e519e77 | ||
|  | 9be00ab848 | ||
|  | c2c22f9ae5 | ||
|  | e65c2661f7 | ||
|  | 88db193b5e | ||
|  | 38824895bb | ||
|  | a78261594d | ||
|  | 646d301554 | ||
|  | e49b22c022 | ||
|  | 8821602728 | ||
|  | eb19e290bb | ||
|  | b477077b9b | ||
|  | 02cfc1d21a | ||
|  | 4c40b447a5 | ||
|  | eaff7b6c5d | ||
|  | 9ee1b5b81b | ||
|  | 49b9972ade | ||
|  | aae003823d | ||
|  | 1369de865a | ||
|  | 8e3cf9c31e | ||
|  | bc31c61fb3 | ||
|  | 975e4a2737 | ||
|  | 8d638158e4 | ||
|  | 1f37eb9962 | ||
|  | 09ea8a046e | ||
|  | 0acb23ab8b | ||
|  | 831974cb63 | ||
|  | 031ac52670 | ||
|  | 14ceb9dc67 | ||
|  | aa86c25e7c | ||
|  | cb0788e44c | ||
|  | 9e88353bd5 | ||
|  | 6db96f0a1d | ||
|  | b01bb3cf7b | ||
|  | 95b9da9377 | ||
|  | 30822e3f4d | ||
|  | 957bf8ffcc | ||
|  | 446ecfd4b5 | ||
|  | 821c9ec3fb | ||
|  | 531a3e263c | ||
|  | 4917208818 | ||
|  | e81cdf3b40 | ||
|  | bffe614b54 | ||
|  | 69ad19757d | ||
|  | a4c0afe277 | ||
|  | cc157a3cf3 | ||
|  | ff2b64d476 | ||
|  | 459a54ac5d | ||
|  | a50821f2e6 | ||
|  | 72e0fc7cbd | ||
|  | 3260d06bc8 | ||
|  | e07db0129f | ||
|  | 3bfa7ff69f | ||
|  | 160fff88ea | ||
|  | 7a658cb3d6 | ||
|  | 25361933d7 | ||
|  | 4ac02a1a7b | ||
|  | 6f15f12b49 | ||
|  | 9e50c1afe2 | ||
|  | 3a43ea10d9 | ||
|  | 3539b1cf23 | ||
|  | d0ba0d011f | ||
|  | 1f07affbef | ||
|  | c83f7de30c | ||
|  | acb5c75379 | ||
|  | a73384c436 | ||
|  | 96542fa42b | ||
|  | 0d885203a7 | ||
|  | e08654bb20 | ||
|  | 486c0a9810 | ||
|  | 0b71c2cb96 | ||
|  | 737576e0ca | ||
|  | db7a99dc59 | ||
|  | 4b48229329 | ||
|  | 1a41b548c4 | ||
|  | 7f58558da4 | ||
|  | 1503e18585 | ||
|  | 94155d2816 | ||
|  | ec42cc2c07 | ||
|  | af79e39492 | ||
|  | 0698c29410 | ||
|  | 08c1854885 | ||
|  | ff39402d82 | ||
|  | 4e375131d1 | ||
|  | ac9ced2cf4 | ||
|  | e93791e6e8 | ||
|  | fa554cbffc | ||
|  | f317d7723e | ||
|  | 07fd92c9b2 | ||
|  | 91fea83f26 | ||
|  | 471f703634 | ||
|  | 065049f663 | ||
|  | 580d81a621 | ||
|  | 7acf448c0c | ||
|  | bcf4956d2d | ||
|  | 13754c843a | ||
|  | 4743a6882c | ||
|  | 2dbc727728 | ||
|  | 560bedc80b | ||
|  | 7ace959f1f | ||
|  | 95dc7b07e2 | ||
|  | fa20a6cb2a | ||
|  | 36b308e616 | ||
|  | b912835edb | ||
|  | add5dab611 | ||
|  | 5172766216 | ||
|  | fcb55e52fc | ||
|  | 411f7ea25b | ||
|  | e05ad71fff | ||
|  | d08a172227 | ||
|  | 4fff8394d9 | ||
|  | 5bb438c221 | ||
|  | a753017fcd | ||
|  | 5522be228b | ||
|  | 3b729c9a04 | ||
|  | eda2fcd327 | ||
|  | 09e12fc610 | ||
|  | 93152f4073 | ||
|  | 17a1d3fbbb | ||
|  | a0e2b6e8e2 | ||
|  | 79a08a0c9e | ||
|  | a24d6fd4b6 | ||
|  | f220aeb78c | ||
|  | 85e0c68a36 | ||
|  | 113e1b7aa5 | ||
|  | cfcc232432 | ||
|  | 29e9b54cc4 | ||
|  | 0c90d1f185 | ||
|  | b5090ea179 | ||
|  | cfbf20f745 | ||
|  | dd002746aa | ||
|  | 11f4560745 | ||
|  | b702e3a14d | ||
|  | 3b2cfad774 | ||
|  | 9164dc6893 | ||
|  | d3cf36ecb1 | ||
|  | 7d0a0123e0 | ||
|  | 2f025e2e1a | ||
|  | 9bf9e8406b | ||
|  | 6bd1ed6e3a | ||
|  | 0f6205fbc7 | ||
|  | f2a6888fb2 | ||
|  | 5143e21ba6 | ||
|  | 26dc24a1f4 | ||
|  | 350c157493 | ||
|  | 51b879a7c8 | ||
|  | 528126aa29 | ||
|  | 73bfafa7f7 | ||
|  | 686a12e465 | ||
|  | 358beacc65 | ||
|  | 69db0aa29b | ||
|  | 20f44ad2d4 | ||
|  | d73c5e0904 | ||
|  | f322e9bd8f | ||
|  | b8e1645eb8 | ||
|  | 73a908cd71 | ||
|  | a6b7ea4ce8 | ||
|  | 4af08ffffd | ||
|  | dc66fd7384 | ||
|  | 16ff61264b | ||
|  | f3ec17b0b1 | ||
|  | 1c31915627 | ||
|  | 79c96677bd | ||
|  | 54118071cb | ||
|  | 2e28b54936 | ||
|  | f333e2d2c0 | ||
|  | b89d40b39f | ||
|  | 488ae154cf | ||
|  | c1e501c271 | ||
|  | 89d5185c91 | ||
|  | f4bf38bca1 | ||
|  | b87b3f054b | ||
|  | f6137b8b68 | ||
|  | 87309235bd | ||
|  | d37017bfa0 | ||
|  | 5d6440bd50 | ||
|  | a70295f1ff | ||
|  | b681b13468 | ||
|  | 2c536f32cf | ||
|  | 991fe5280f | ||
|  | 174fe5cfb3 | ||
|  | 119284f256 | ||
|  | 05680af726 | ||
|  | fba82f670b | ||
|  | afa2ebd004 | ||
|  | f0eeff3493 | ||
|  | 44d64c4e0f | ||
|  | 86ece3b70d | ||
|  | 6cdad71693 | ||
|  | cf6d5ea22e | ||
|  | ecf227daa6 | ||
|  | 4b5a156865 | ||
|  | cfae369fd3 | ||
|  | b05eabbbf8 | ||
|  | fd178eee43 | ||
|  | aef2ef34fb | ||
|  | fcbffa1189 | ||
|  | 0d8240381e | ||
|  | 77da6679ef | ||
|  | 5845254caa | ||
|  | 5d92d7cfcc | ||
|  | 67f6649242 | ||
|  | 1438ae5e4f | ||
|  | 01c351e64b | ||
|  | b0c21dfcb8 | ||
|  | f4cddbc87f | ||
|  | cfce176c82 | ||
|  | 782b1a10c3 | ||
|  | 2d9cd0e4e1 | ||
|  | 908ba09e2a | ||
|  | 4a84ef4b3c | ||
|  | f3353327dd | ||
|  | 316433f592 | ||
|  | 1ccdea0c87 | ||
|  | 7d9af0c4c7 | ||
|  | a1247cdae5 | ||
|  | 5696192cce | ||
|  | 91826b730f | ||
|  | 48b1cf817a | ||
|  | 94ba9f0af2 | ||
|  | 333f27a016 | ||
|  | b67379a6c3 | ||
|  | 1e6188cf24 | ||
|  | 12e859edd5 | ||
|  | e000dcfc51 | ||
|  | e7a06159bb | ||
|  | c900b6384d | ||
|  | 970a0ae254 | ||
|  | 5e0ac0abc7 | ||
|  | bcd6711965 | ||
|  | bb63a4df9f | ||
|  | b4d7349583 | ||
|  | fed8a3fd2d | ||
|  | bc6e7174ec | ||
|  | 075f98883a | ||
|  | c9df3a6176 | ||
|  | b2d2a79b00 | ||
|  | 26d8283930 | ||
|  | 4079c0a32c | ||
|  | 6d72a94fe4 | ||
|  | 227a7a027f | ||
|  | 9e595b92ba | ||
|  | c346058631 | ||
|  | f2678ea33d | ||
|  | 45a43d6998 | ||
|  | cb9888af7a | ||
|  | 93b183254f | ||
|  | e92b305a56 | ||
|  | 5b0628da15 | ||
|  | 8d087a8e84 | ||
|  | 1715926c1c | ||
|  | c8b69efca2 | ||
|  | 7f6011720c | ||
|  | bdc5f0269f | ||
|  | a6054e825e | ||
|  | 156e76fe0a | ||
|  | 89587a4c78 | ||
|  | 29cc73e83b | ||
|  | e56cdb1dcb | ||
|  | 9107a08011 | ||
|  | 0242587a5f | ||
|  | 3ce27023c2 | ||
|  | 174958c6b1 | ||
|  | c71e8b1e8e | ||
|  | ada067a2a0 | ||
|  | f4c4f6bc93 | ||
|  | 6eeb6fd209 | ||
|  | efba3c496a | ||
|  | 27a0ab52e6 | ||
|  | d6ce859fcd | ||
|  | e5f69c9536 | ||
|  | d8a2225038 | ||
|  | 7b91a679f5 | ||
|  | 840658b590 | ||
|  | c5e7ef855e | ||
|  | 170227c97f | ||
|  | 379803278b | ||
|  | 3956d8efa7 | ||
|  | 269aceaf1a | ||
|  | 41127565fc | ||
|  | 0e405d2f90 | ||
|  | f67810b288 | ||
|  | 4095d9e79d | ||
|  | 547a4c2353 | ||
|  | 9120e4d53c | ||
|  | 1a62b76104 | ||
|  | cc7f277bef | ||
|  | 274df8dfd1 | ||
|  | 2e256f4ff5 | ||
|  | d10111d2ab | ||
|  | 0c2b266f5e | ||
|  | 16b4dbae18 | ||
|  | 88409dcc99 | ||
|  | 5cacff39b7 | ||
|  | 093723a9d0 | ||
|  | f67ce6be44 | ||
|  | 1be29b8b7a | ||
|  | dfdbcdc9f3 | ||
|  | bd725e2dc4 | ||
|  | dd9d9144dd | ||
|  | 1a429e4bb6 | ||
|  | ad6fb5e926 | ||
|  | 72cb7049ba | ||
|  | 2e42f3afac | ||
|  | 8474c9f5bc | ||
|  | b7ed03f7e8 | ||
|  | a44b6b5e4c | ||
|  | 5268f75e17 | ||
|  | 687d1621fc | ||
|  | a6944aa6e3 | ||
|  | b8365c76a1 | ||
|  | 19216cc174 | ||
|  | c6a23b9b8e | ||
|  | c0f42fb1eb | ||
|  | 160873e2cc | ||
|  | 8df859bbef | ||
|  | e30d10a85d | ||
|  | bafc3f1db6 | ||
|  | 03571fcf74 | ||
|  | e7a6802488 | ||
|  | a6e6b5e158 | ||
|  | 74c3d925e6 | ||
|  | f67fa4d7f0 | ||
|  | 225a8c0e08 | ||
|  | 9a6ce27fe1 | ||
|  | 437114704a | ||
|  | 9b4cac4e56 | ||
|  | e780513b35 | ||
|  | d582c59a7d | ||
|  | 7d2c85f617 | ||
|  | 4a882bc4ac | ||
|  | 3c4eac1fcd | ||
|  | 75d5a15bdb | ||
|  | 17f121f7d1 | ||
|  | a66c570b51 | ||
|  | e1e7f2eaf6 | ||
|  | 46d2dce372 | ||
|  | c39f1f6d4c | ||
|  | 738b04f2d9 | ||
|  | ecba1c3135 | ||
|  | 748226f4ed | ||
|  | f658f53ba9 | ||
|  | 4a8304ee4f | ||
|  | 7fe9604338 | ||
|  | 7f09a6dae8 | ||
|  | 270129bc97 | ||
|  | 62ec6f6bef | ||
|  | b24c14900c | ||
|  | 44c55ce3a6 | ||
|  | 895bea048c | ||
|  | 885ab5bdc9 | ||
|  | c6355b2c09 | ||
|  | ada6bf3f24 | ||
|  | db471ac679 | ||
|  | 84059c4085 | ||
|  | 09b363f943 | ||
|  | fd347c7780 | ||
|  | b38a34efa5 | ||
|  | 10e24cf30e | ||
|  | 3369057afb | ||
|  | 7ada56e215 | ||
|  | 373b9e6d53 | ||
|  | 3de31de9f7 | ||
|  | 7252259ab3 | ||
|  | f33c673a05 | ||
|  | 65a7958b66 | ||
|  | 91e4df7478 | ||
|  | 399606d9a0 | ||
|  | 559afcaffd | ||
|  | 15f5f91ddb | ||
|  | 067a8063a5 | ||
|  | 35e367ba24 | ||
|  | fe68d991fc | ||
|  | 88dc633e8e | ||
|  | 0b9cbb0f5f | ||
|  | c3a33b4402 | ||
|  | e12f00aad6 | ||
|  | 47bac0185f | ||
|  | dafe1dab2a | ||
|  | 8ba4ea2819 | ||
|  | f6f708fd5b | ||
|  | 1203077aed | ||
|  | ddbf79c928 | ||
|  | aa47dd579f | ||
|  | cdce478d77 | ||
|  | f11f4a59f1 | ||
|  | d631e8df1e | ||
|  | af80d40700 | ||
|  | 3a6f8a945a | ||
|  | bb9727d54a | ||
|  | 7da8b3e545 | ||
|  | b6968df451 | ||
|  | b85bbdd0ad | ||
|  | aa986fb948 | ||
|  | 6e63de6e99 | ||
|  | 962ecd08fa | ||
|  | 580a26b3ea | ||
|  | 0cda6b5fa0 | ||
|  | cc8c3795b8 | ||
|  | 3c21165efb | ||
|  | 818fdf4670 | ||
|  | b7d679b492 | ||
|  | a42093df23 | ||
|  | 6157db51da | ||
|  | 843052989b | ||
|  | 8e01fa3192 | ||
|  | e2126a7ea7 | ||
|  | 0b46d14650 | ||
|  | 9e28deedf7 | ||
|  | 9c0172b413 | ||
|  | 2933d022aa | ||
|  | bdefb7193e | ||
|  | 7246133363 | ||
|  | 69b68a6975 | ||
|  | e38a7e8cdd | ||
|  | dbd9dd0001 | ||
|  | ccb1743a01 | ||
|  | 4ac897693c | ||
|  | dfb962e9fe | ||
|  | f97cbd778d | ||
|  | e889a61d43 | ||
|  | a33d422547 | ||
|  | c4b4001b96 | ||
|  | 20949b641e | ||
|  | 56e2174b6d | ||
|  | acfeebca14 | ||
|  | cc47acbd3f | ||
|  | 869e16406d | ||
|  | 1c99da4fc5 | ||
|  | b74ca4e702 | ||
|  | 5fde7451c8 | ||
|  | 6fd9209336 | ||
|  | 0213dc3560 | ||
|  | e73c434c1b | ||
|  | b135f3fa50 | ||
|  | 0e580a3180 | ||
|  | 8e3cb43244 | ||
|  | 6dcfb92dba | ||
|  | a7ea5773da | ||
|  | 5b66131d30 | ||
|  | cac06dda7d | ||
|  | 12c1c97a99 | ||
|  | af4b4243ea | ||
|  | 5189c74058 | ||
|  | fffd32e13c | ||
|  | b37a5095f0 | ||
|  | 951b4c198c | ||
|  | 8698eed273 | ||
|  | 1af8614b35 | ||
|  | 11faeeaf45 | ||
|  | 9d0fcc3370 | ||
|  | 2f6d6037de | ||
|  | 1d69bcc814 | ||
|  | 62c363068b | ||
|  | d2e0b8187b | ||
|  | 628b98af9b | ||
|  | 627e3dbd7f | ||
|  | 61b88f6bf8 | ||
|  | 8ab37e73c1 | ||
|  | afdf43ebf1 | ||
|  | 2e0d1c059d | ||
|  | 2b772199b0 | ||
|  | c37b850798 | ||
|  | c31f924d77 | ||
|  | ed87a6167d | ||
|  | 0d86ba0ca9 | ||
|  | f2fd283d06 | ||
|  | 10b978debf | ||
|  | 85756713df | ||
|  | 3bdfd6a78a | ||
|  | e59ab4a320 | ||
|  | 15b3902bd3 | ||
|  | b622d66942 | ||
|  | 12ef219064 | ||
|  | 853b52b52d | ||
|  | 7a2ad1d31a | ||
|  | c5ad476eaf | ||
|  | 12978ff410 | ||
|  | caa45097c3 | ||
|  | 232e3a117c | ||
|  | 3668382911 | ||
|  | fafa53ffb7 | ||
|  | 44673fd57e | ||
|  | f7149f225c | ||
|  | 6773e18d34 | ||
|  | 19a9b06f3c | ||
|  | 7ee2273761 | ||
|  | 86b8297c9d | ||
|  | 17533112a1 | ||
|  | 2cc1a338ec | ||
|  | 81731247c7 | ||
|  | 65c85aacf0 | ||
|  | dca23151a8 | ||
|  | 25305760c5 | ||
|  | 3ef2459ad0 | ||
|  | 29a56f42a8 | ||
|  | 3784348f91 | ||
|  | 188ec03abd | 
							
								
								
									
										27
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,27 @@ | |||||||
|  | # Gradle | ||||||
|  | .gradle | ||||||
|  | build | ||||||
|  |  | ||||||
|  | # VSC | ||||||
|  | bin | ||||||
|  | .vscode | ||||||
|  |  | ||||||
|  | # IntelliJ | ||||||
|  | *.iml | ||||||
|  | .idea | ||||||
|  | out | ||||||
|  |  | ||||||
|  | # Eclipse | ||||||
|  | .classpath | ||||||
|  | .project | ||||||
|  |  | ||||||
|  | # Libraries | ||||||
|  | *.so | ||||||
|  | *.dylib | ||||||
|  | *.dll | ||||||
|  | *.jar | ||||||
|  | *.class | ||||||
|  |  | ||||||
|  | .DS_Store | ||||||
|  | !Projekte/gradle/wrapper/gradle-wrapper.jar | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								Dokumente/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | # | ||||||
							
								
								
									
										3
									
								
								Projekte/.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | *.bat           text eol=crlf | ||||||
|  | gradlew         text eol=lf | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								Projekte/.run/BattleshipApp (Mac).run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | <component name="ProjectRunConfigurationManager"> | ||||||
|  |     <configuration default="false" name="BattleshipApp (Mac)" type="Application" factoryName="Application" | ||||||
|  |                    singleton="false"> | ||||||
|  |         <option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp"/> | ||||||
|  |         <module name="Projekte.battleship.client.main"/> | ||||||
|  |         <option name="VM_PARAMETERS" value="-XstartOnFirstThread"/> | ||||||
|  |         <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/> | ||||||
|  |         <extension name="coverage"> | ||||||
|  |             <pattern> | ||||||
|  |                 <option name="PATTERN" value="pp.battleship.client.*"/> | ||||||
|  |                 <option name="ENABLED" value="true"/> | ||||||
|  |             </pattern> | ||||||
|  |         </extension> | ||||||
|  |         <method v="2"> | ||||||
|  |             <option name="Make" enabled="true"/> | ||||||
|  |         </method> | ||||||
|  |     </configuration> | ||||||
|  | </component> | ||||||
							
								
								
									
										18
									
								
								Projekte/.run/BattleshipApp.run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | <component name="ProjectRunConfigurationManager"> | ||||||
|  |   <configuration default="false" name="BattleshipApp" type="Application" factoryName="Application" singleton="false" | ||||||
|  |                  nameIsGenerated="true"> | ||||||
|  |     <option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp"/> | ||||||
|  |     <module name="Projekte.battleship.client.main"/> | ||||||
|  |     <option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties"/> | ||||||
|  |     <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/> | ||||||
|  |     <extension name="coverage"> | ||||||
|  |       <pattern> | ||||||
|  |         <option name="PATTERN" value="pp.battleship.client.*"/> | ||||||
|  |         <option name="ENABLED" value="true"/> | ||||||
|  |       </pattern> | ||||||
|  |     </extension> | ||||||
|  |     <method v="2"> | ||||||
|  |       <option name="Make" enabled="true"/> | ||||||
|  |     </method> | ||||||
|  |   </configuration> | ||||||
|  | </component> | ||||||
							
								
								
									
										17
									
								
								Projekte/.run/BattleshipServer.run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,17 @@ | |||||||
|  | <component name="ProjectRunConfigurationManager"> | ||||||
|  |   <configuration default="false" name="BattleshipServer" type="Application" factoryName="Application" | ||||||
|  |                  nameIsGenerated="true"> | ||||||
|  |     <option name="MAIN_CLASS_NAME" value="pp.battleship.server.BattleshipServer"/> | ||||||
|  |     <module name="Projekte.battleship.server.main"/> | ||||||
|  |     <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/> | ||||||
|  |     <extension name="coverage"> | ||||||
|  |       <pattern> | ||||||
|  |         <option name="PATTERN" value="pp.battleship.server.*"/> | ||||||
|  |         <option name="ENABLED" value="true"/> | ||||||
|  |       </pattern> | ||||||
|  |     </extension> | ||||||
|  |     <method v="2"> | ||||||
|  |       <option name="Make" enabled="true"/> | ||||||
|  |     </method> | ||||||
|  |   </configuration> | ||||||
|  | </component> | ||||||
							
								
								
									
										18
									
								
								Projekte/.run/MonopolyApp (Mac).run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | <component name="ProjectRunConfigurationManager"> | ||||||
|  |     <configuration default="false" name="MonopolyApp (Mac)" type="Application" factoryName="Application" | ||||||
|  |                    singleton="false"> | ||||||
|  |         <option name="MAIN_CLASS_NAME" value="pp.monopoly.client.MonopolyApp"/> | ||||||
|  |         <module name="Projekte.monopoly.client.main"/> | ||||||
|  |         <option name="VM_PARAMETERS" value="-XstartOnFirstThread"/> | ||||||
|  |         <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/> | ||||||
|  |         <extension name="coverage"> | ||||||
|  |             <pattern> | ||||||
|  |                 <option name="PATTERN" value="pp.monopoly.client.*"/> | ||||||
|  |                 <option name="ENABLED" value="true"/> | ||||||
|  |             </pattern> | ||||||
|  |         </extension> | ||||||
|  |         <method v="2"> | ||||||
|  |             <option name="Make" enabled="true"/> | ||||||
|  |         </method> | ||||||
|  |     </configuration> | ||||||
|  | </component> | ||||||
							
								
								
									
										18
									
								
								Projekte/.run/MonopolyApp.run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | <component name="ProjectRunConfigurationManager"> | ||||||
|  |   <configuration default="false" name="MonopolyApp" type="Application" factoryName="Application" singleton="false" | ||||||
|  |                  nameIsGenerated="true"> | ||||||
|  |     <option name="MAIN_CLASS_NAME" value="pp.monopoly.client.MonopolyApp"/> | ||||||
|  |     <module name="Projekte.monopoly.client.main"/> | ||||||
|  |     <option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties"/> | ||||||
|  |     <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/> | ||||||
|  |     <extension name="coverage"> | ||||||
|  |       <pattern> | ||||||
|  |         <option name="PATTERN" value="pp.monopoly.client.*"/> | ||||||
|  |         <option name="ENABLED" value="true"/> | ||||||
|  |       </pattern> | ||||||
|  |     </extension> | ||||||
|  |     <method v="2"> | ||||||
|  |       <option name="Make" enabled="true"/> | ||||||
|  |     </method> | ||||||
|  |   </configuration> | ||||||
|  | </component> | ||||||
							
								
								
									
										17
									
								
								Projekte/.run/MonopolyServer.run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,17 @@ | |||||||
|  | <component name="ProjectRunConfigurationManager"> | ||||||
|  |   <configuration default="false" name="MonopolyServer" type="Application" factoryName="Application" | ||||||
|  |                  nameIsGenerated="true"> | ||||||
|  |     <option name="MAIN_CLASS_NAME" value="pp.monopoly.server.MonopolyServer"/> | ||||||
|  |     <module name="Projekte.monopoly.server.main"/> | ||||||
|  |     <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/> | ||||||
|  |     <extension name="coverage"> | ||||||
|  |       <pattern> | ||||||
|  |         <option name="PATTERN" value="pp.monopoly.server.*"/> | ||||||
|  |         <option name="ENABLED" value="true"/> | ||||||
|  |       </pattern> | ||||||
|  |     </extension> | ||||||
|  |     <method v="2"> | ||||||
|  |       <option name="Make" enabled="true"/> | ||||||
|  |     </method> | ||||||
|  |   </configuration> | ||||||
|  | </component> | ||||||
							
								
								
									
										24
									
								
								Projekte/.run/Projekte [test].run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,24 @@ | |||||||
|  | <component name="ProjectRunConfigurationManager"> | ||||||
|  |   <configuration default="false" name="Projekte [test]" type="GradleRunConfiguration" factoryName="Gradle"> | ||||||
|  |     <ExternalSystemSettings> | ||||||
|  |       <option name="executionName" /> | ||||||
|  |       <option name="externalProjectPath" value="$PROJECT_DIR$" /> | ||||||
|  |       <option name="externalSystemIdString" value="GRADLE" /> | ||||||
|  |       <option name="scriptParameters" value="--continue" /> | ||||||
|  |       <option name="taskDescriptions"> | ||||||
|  |         <list /> | ||||||
|  |       </option> | ||||||
|  |       <option name="taskNames"> | ||||||
|  |         <list> | ||||||
|  |           <option value="test" /> | ||||||
|  |         </list> | ||||||
|  |       </option> | ||||||
|  |       <option name="vmOptions" /> | ||||||
|  |     </ExternalSystemSettings> | ||||||
|  |     <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> | ||||||
|  |     <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> | ||||||
|  |     <DebugAllEnabled>false</DebugAllEnabled> | ||||||
|  |     <RunAsTest>false</RunAsTest> | ||||||
|  |     <method v="2" /> | ||||||
|  |   </configuration> | ||||||
|  | </component> | ||||||
							
								
								
									
										189
									
								
								Projekte/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,189 @@ | |||||||
|  | # Beispielprogramme des Programmierprojekts | ||||||
|  |  | ||||||
|  | Hier ist der Quellcode für das in der Einarbeitungsphase genutzte Spiel | ||||||
|  | _Battleships_ zu finden. Die Quellen bestehen aus den folgenden | ||||||
|  | Gradle-Unterprojekten: | ||||||
|  |  | ||||||
|  | * _:battleship:server_ | ||||||
|  | * _:battleship:client_ | ||||||
|  | * _:battleship:model_ | ||||||
|  | * _:battleship:converter_ | ||||||
|  | * _:common_ | ||||||
|  | * _:jme-common_ | ||||||
|  |  | ||||||
|  | _Battleships_ ist ein netzwerkbasiertes Spiel und besteht aus einem Server- und | ||||||
|  | einem Clientanteil, die in den Unterprojekten _:battleship:server_ und | ||||||
|  | _:battleship:client_ realisiert sind. Beide nutzen das Unterprojekt | ||||||
|  | _:battleship:model_, das den gemeinsamen Modellanteil enthält. | ||||||
|  |  | ||||||
|  | Die Unterprojekte _:common_ und _:jme-common_ enthalten Hilfsklassen. | ||||||
|  |  | ||||||
|  | Das Unterprojekt _:battleship:converter_ wird für _Battleships_ selbst nicht | ||||||
|  | benötigt, sondern enthält lediglich den Code, um ein im Spiel verwendetes | ||||||
|  | 3d-Modell eines Schlachtschiffs in eine _J3O_-Datei umzuwandeln, die von jME | ||||||
|  | einfacher geladen werden kann. | ||||||
|  |  | ||||||
|  | ## 1 Vorbereitung | ||||||
|  |  | ||||||
|  | Für das Programmierprojekt empfehlen wir die Verwendung von Java 20. Unter Linux | ||||||
|  | sollte [_Eclipse Temurin_](https://adoptium.net/temurin/releases/?version=20) | ||||||
|  | als JDK verwendet werden, andere JDKs können unter Linux Probleme verursachen. | ||||||
|  | Auf anderen Betriebssystemen empfehlen wir aber ebenfalls Temurin. Im Folgenden | ||||||
|  | ist beschrieben, wie Sie Temurin installieren und die Umgebungsvariable | ||||||
|  | **JAVA_HOME** richtig setzen, damit Sie Gradle (siehe unten) verwenden können. | ||||||
|  |  | ||||||
|  | ### 1.1 Installation von Temurin | ||||||
|  |  | ||||||
|  | Laden Sie [_Eclipse Temurin_](https://adoptium.net/temurin/releases/?version=20) | ||||||
|  | entsprechend Ihrem Betriebssystem und Ihrer Prozessorarchitektur herunter und | ||||||
|  | entpacken Sie das Archiv in einem Verzeichnis Ihrer Wahl auf Ihrem Rechner. | ||||||
|  |  | ||||||
|  | ### 1.2 Setzen von JAVA_HOME | ||||||
|  |  | ||||||
|  | Zur Verwendung mit Gradle muss die Umgebungsvariable **JAVA_HOME** richtig | ||||||
|  | gesetzt werden. Folgen Sie dazu den nachfolgenden Anweisungen entsprechend Ihrem | ||||||
|  | Betriebssystem: | ||||||
|  |  | ||||||
|  | * **Windows:** | ||||||
|  |  | ||||||
|  |   Öffnen Sie ihre Powershell (Core) bzw. ihr Windows Terminal mit Powershell | ||||||
|  |   (Core). Überprüfen Sie, ob die Umgebungsvariable korrekt gesetzt ist:   | ||||||
|  |   `Get-ChildItem -Path Env:JAVA_HOME`   | ||||||
|  |   Falls kein oder ein falscher Pfad gesetzt ist, setzen Sie diesen mit dem | ||||||
|  |   folgenden Kommando (in einer Zeile):   | ||||||
|  |   `[System.Environment]::SetEnvironmentVariable('JAVA_HOME','<Pfad zum SDK>',[System.EnvironmentVariableTarget]::User)` | ||||||
|  |  | ||||||
|  |   Alternativ können Sie die GUI verwenden. Unter Windows 10 klicken Sie die | ||||||
|  |   Windows-Taste und dann das Zahnrad um die Einstellungen zu öffnen. Dort wählen | ||||||
|  |   Sie "System", dann "Info" (links unten) und nun | ||||||
|  |   "Erweiterte Systemeinstellungen" (rechts) um den Dialog "Systemeigenschaften" | ||||||
|  |   zu starten. Im Reiter "Erweitert" klicken Sie | ||||||
|  |   "Umgebungsvariablen..." und klicken dann unter "Benutzervariablen" den Knopf | ||||||
|  |   "Neu..." um JAVA_HOME anzulegen oder "Bearbeiten" um ihn zu ändern. Geben Sie | ||||||
|  |   als Name `JAVA_HOME` und als Wert den Pfad ein. Schließen Sie mit "OK". | ||||||
|  |  | ||||||
|  |   > **(!) Beachten Sie, dass Sie die jeweilige Applikation neu starten müssen**, | ||||||
|  |   > um von der gesetzten Umgebungsvariablen Notiz zu nehmen. | ||||||
|  |   > Dies betrifft auch die Shell, die Sie gerade verwenden. | ||||||
|  |  | ||||||
|  | * **UNIX (Linux/MacOS):** | ||||||
|  |  | ||||||
|  |   Öffnen oder erstellen Sie die Datei `~/.profile` (wenn Sie die Bash verwenden; | ||||||
|  |   bei anderen Shells sind es andere Dateien) und ergänzen Sie am Ende der Datei | ||||||
|  |   die Zeile: | ||||||
|  |  | ||||||
|  |   `export JAVA_HOME="<Pfad zum entpackten Archiv>"` | ||||||
|  |  | ||||||
|  |   Ersetzen Sie dabei `<Pfad zum entpackten Archiv>` mit dem entsprechenden Pfad. | ||||||
|  |   Zum Beispiel: | ||||||
|  |  | ||||||
|  |   `export JAVA_HOME="/home/user/jdk-20.0.2"` | ||||||
|  |  | ||||||
|  |   Fügen Sie dann die folgende Zeile hinzu: | ||||||
|  |  | ||||||
|  |   `export PATH="$JAVA_HOME/bin:$PATH"` | ||||||
|  |  | ||||||
|  | ## 2 Programmstart | ||||||
|  |  | ||||||
|  | Grundsätzlich kann man das gesamte Projekt einfach in IntelliJ öffnen. Details | ||||||
|  | dazu sind im Aufgabenblatt zur Einarbeitungsaufgabe zu finden. Im Folgenden ist | ||||||
|  | beschrieben, wie die _Batttleships_ unmittelbar von der Kommandozeile gestartet | ||||||
|  | werden können. | ||||||
|  |  | ||||||
|  | Um _Battleships_ spielen zu können, muss man zuerst das Server-Programm auf | ||||||
|  | einem Rechner und dann zweimal das Client-Programm auf beliebigen Rechnern | ||||||
|  | starten, die TCP/IP-Verbindungen zum Server erlauben. Natürlich ist es auch | ||||||
|  | möglich, alle drei Programme auf demselben Rechner zu starten. | ||||||
|  |  | ||||||
|  | Es empfiehlt sich der Start von der Kommandozeile. Will man alle drei Programme | ||||||
|  | auf demselben Rechner starten, sollte man dazu drei Shell-Instanzen öffnen und | ||||||
|  | in jeder eines der Programme starten. Auf diese Weise können die | ||||||
|  | Logging-Ausgaben der drei Programme voneinander unterschieden werden. | ||||||
|  |  | ||||||
|  | Das Server-Programm startet man unmittelbar mit Gradle mit | ||||||
|  |  | ||||||
|  | `./gradlew :battleship:server:run` | ||||||
|  |  | ||||||
|  | Unter Windows kann es je nach Shell (Eingabeaufforderung cmd) erforderlich sein, | ||||||
|  | `/` jeweils durch `\ ` zu ersetzen. | ||||||
|  |  | ||||||
|  | Im Verzeichnis `battleship/server` befindet sich die Datei `config.propeties`, | ||||||
|  | worüber sich der Server konfigurieren lässt. Mit der Zeile `port=1234` lässt | ||||||
|  | sich der verwendete Server-Port (hier 1234) einstellen. Außerdem befindet sich | ||||||
|  | dort die Datei `logging.properties`, womit das Logging des Servers konfiguriert | ||||||
|  | wird. | ||||||
|  |  | ||||||
|  | Das Client-Programm startet man unmittelbar mit Gradle mit | ||||||
|  |  | ||||||
|  | `./gradlew :battleship:client:run` | ||||||
|  |  | ||||||
|  | Die Datei `logging.properties` im Verzeichnis `battleship/client` konfiguriert | ||||||
|  | das Logging des Clients. | ||||||
|  |  | ||||||
|  | Alternativ kann man auch die Start-Skripte | ||||||
|  |  | ||||||
|  | * `./battleship/server/build/install/battleship-server/bin/battleship-server` | ||||||
|  | * `./battleship/client/build/install/battleship/bin/battleship` | ||||||
|  |  | ||||||
|  | direkt in der Kommandozeile starten. Allerdings müssen sie zuvor mittels | ||||||
|  |  | ||||||
|  | `./gradlew installDist` | ||||||
|  |  | ||||||
|  | erzeugt worden sein. Beachten Sie aber, dass nur im **aktuellen | ||||||
|  | Arbeitsverzeichnis** nach den Dateien `config.properties` und | ||||||
|  | `logging.properties` gesucht wird und diese geladen werden. Das heißt, dass die | ||||||
|  | vordefinierten Dateien in den Verzeichnissen `battleship/server` und | ||||||
|  | `battleship/client` nur dann gelesen werden, wenn Sie diese Verzeichnisse als | ||||||
|  | aktuelle Arbeitsverzeichnisse nutzen. Wie üblich müssen Sie dazu in der | ||||||
|  | Kommandozeile | ||||||
|  |  | ||||||
|  | `cd battleship/server` | ||||||
|  |  | ||||||
|  | bzw. | ||||||
|  |  | ||||||
|  | `cd battleship/client` | ||||||
|  |  | ||||||
|  | eingeben. | ||||||
|  |  | ||||||
|  | ## 3 Hinweise zu _Battleships_ | ||||||
|  |  | ||||||
|  | Der _Battleships_-Client hat ein Menü, in das man immer mit der | ||||||
|  | Esc-Taste kommt. Aus dem Menü heraus lässt sich das Programm auch schließen. | ||||||
|  | Beachte, dass sich beim Laden und Speichern eines Spiels kein Dateidialog | ||||||
|  | öffnet. Vielmehr muss man den Dateipfad in das Dialogfeld eingeben. Da | ||||||
|  | JSON-Dateien geschrieben werden, empfiehlt sich das Datei-Suffix _.json_. | ||||||
|  |  | ||||||
|  | ## 4 Allgemeine Gradle-Tasks: | ||||||
|  |  | ||||||
|  | - `./gradlew clean` | ||||||
|  |  | ||||||
|  |   Entfernt alle `build`-Verzeichnisse und alle erzeugten Dateien. | ||||||
|  |  | ||||||
|  | - `./gradlew classes` | ||||||
|  |  | ||||||
|  |   Übersetzt den Quellcode und legt unter build den Bytecode sowie | ||||||
|  |   Ressourcen ab. | ||||||
|  |  | ||||||
|  | - `./gradlew javadoc` | ||||||
|  |  | ||||||
|  |   Erzeugt die Dokumentation aus den JavaDoc-Kommentaren im Verzeichnis | ||||||
|  |   `build/docs/javadoc` des jeweiligen Unterprojekts. | ||||||
|  |  | ||||||
|  | - `./gradlew test` | ||||||
|  |  | ||||||
|  |   Führt die JUnit-Tests durch. Ergebnisse sind im Verzeichnis | ||||||
|  |   `build/reports/tests` des jeweiligen Unterprojekts zu finden. | ||||||
|  |  | ||||||
|  | - `./gradlew build` | ||||||
|  |  | ||||||
|  |   Führt die JUnit-Tests durch und erstellt in `build/distributions` | ||||||
|  |   gepackte Distributionsdateien | ||||||
|  |  | ||||||
|  | - `./gradlew installDist` | ||||||
|  |  | ||||||
|  |   Erstellt unter `battleship/client/build/install` und | ||||||
|  |   `battleship/server/build/install` Verzeichnisse, die jeweils eine ausführbare | ||||||
|  |   Distribution samt Start-Skripten enthält (siehe oben). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  | Juli 2024 | ||||||
							
								
								
									
										23
									
								
								Projekte/battleship/client/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,23 @@ | |||||||
|  | plugins { | ||||||
|  |     id 'buildlogic.jme-application-conventions' | ||||||
|  | } | ||||||
|  |  | ||||||
|  | description = 'Battleship Client' | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     implementation project(":jme-common") | ||||||
|  |     implementation project(":battleship:model") | ||||||
|  |  | ||||||
|  |     implementation libs.jme3.desktop | ||||||
|  |     implementation libs.jme3.effects | ||||||
|  |  | ||||||
|  |     runtimeOnly libs.jme3.awt.dialogs | ||||||
|  |     runtimeOnly libs.jme3.plugins | ||||||
|  |     runtimeOnly libs.jme3.jogg | ||||||
|  |     runtimeOnly libs.jme3.testdata | ||||||
|  | } | ||||||
|  |  | ||||||
|  | application { | ||||||
|  |     mainClass = 'pp.battleship.client.BattleshipApp' | ||||||
|  |     applicationName = 'battleship' | ||||||
|  | } | ||||||
							
								
								
									
										73
									
								
								Projekte/battleship/client/client.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,73 @@ | |||||||
|  | ######################################## | ||||||
|  | ## Programming project code | ||||||
|  | ## UniBw M, 2022, 2023, 2024 | ||||||
|  | ## www.unibw.de/inf2 | ||||||
|  | ## (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | ######################################## | ||||||
|  | # | ||||||
|  | # Battleship client configuration | ||||||
|  | # | ||||||
|  | # Specifies the map used by the opponent in single mode. | ||||||
|  | # Single mode is activated if this property is set. | ||||||
|  | #map.opponent=maps/map2.json | ||||||
|  | # | ||||||
|  | # Specifies the map used by the player in single mode. | ||||||
|  | # The player must define their own map if this property is not set. | ||||||
|  | map.own=maps/map1.json | ||||||
|  | # | ||||||
|  | # Coordinates of the shots fired by the RobotClient in the order listed. | ||||||
|  | # Example: | ||||||
|  | #   2, 0,\ | ||||||
|  | #   2, 1,\ | ||||||
|  | #   2, 2,\ | ||||||
|  | #   2, 3 | ||||||
|  | #  defines four shots, namely at the coordinates | ||||||
|  | #  (x=2, y=0), (x=2, y=1), (x=2, y=2), and (x=2, y=3) | ||||||
|  | robot.targets=2, 0,\ | ||||||
|  |               2, 1,\ | ||||||
|  |               2, 2,\ | ||||||
|  |               2, 3 | ||||||
|  | # | ||||||
|  | # Delay in milliseconds between each shot fired by the RobotClient. | ||||||
|  | robot.delay=500 | ||||||
|  | # | ||||||
|  | # The dimensions of the game map used in single mode. | ||||||
|  | # 'map.width' defines the number of columns, and 'map.height' defines the number of rows. | ||||||
|  | map.width=10 | ||||||
|  | map.height=10 | ||||||
|  | # | ||||||
|  | # The number of ships of each length available in single mode. | ||||||
|  | # The value is a comma-separated list where each element corresponds to the number of ships | ||||||
|  | # with a specific length. For example: | ||||||
|  | # ship.nums=4, 3, 2, 1 | ||||||
|  | # This configuration means: | ||||||
|  | #   - 4 ships of length 1 | ||||||
|  | #   - 3 ships of length 2 | ||||||
|  | #   - 2 ships of length 3 | ||||||
|  | #   - 1 ship of length 4 | ||||||
|  | ship.nums=4, 3, 2, 1 | ||||||
|  | # | ||||||
|  | # Screen settings | ||||||
|  | # | ||||||
|  | # Color of the text displayed at the top of the overlay. | ||||||
|  | # The format is (red, green, blue, alpha) where each value ranges from 0 to 1. | ||||||
|  | overlay.top.color=1, 1, 1, 1 | ||||||
|  | # | ||||||
|  | # Application settings configuration | ||||||
|  | # Determines whether the settings window is shown at startup. | ||||||
|  | settings.show=false | ||||||
|  | # | ||||||
|  | # Specifies the width of the application window in pixels. | ||||||
|  | settings.resolution.width=1200 | ||||||
|  | # | ||||||
|  | # Specifies the height of the application window in pixels. | ||||||
|  | settings.resolution.height=800 | ||||||
|  | # | ||||||
|  | # Determines whether the application runs in full-screen mode. | ||||||
|  | settings.full-screen=false | ||||||
|  | # | ||||||
|  | # Enables or disables gamma correction to improve color accuracy. | ||||||
|  | settings.use-gamma-correction=true | ||||||
|  | # | ||||||
|  | # Indicates whether the statistics window is displayed during gameplay. | ||||||
|  | statistics.show=false | ||||||
							
								
								
									
										8
									
								
								Projekte/battleship/client/logging.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | |||||||
|  | handlers=java.util.logging.ConsoleHandler | ||||||
|  | .level=INFO | ||||||
|  | pp.level=FINE | ||||||
|  | com.jme3.network.level=INFO | ||||||
|  | ;com.jme3.util.TangentBinormalGenerator.level=SEVERE | ||||||
|  | java.util.logging.ConsoleHandler.level=FINER | ||||||
|  | java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter | ||||||
|  | ;java.util.logging.SimpleFormatter.format=[%4$s %2$s] %5$s%n | ||||||
							
								
								
									
										66
									
								
								Projekte/battleship/client/maps/map1.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,66 @@ | |||||||
|  | { | ||||||
|  |   "width": 10, | ||||||
|  |   "height": 10, | ||||||
|  |   "ships": [ | ||||||
|  |     { | ||||||
|  |       "length": 4, | ||||||
|  |       "x": 2, | ||||||
|  |       "y": 8, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 3, | ||||||
|  |       "x": 2, | ||||||
|  |       "y": 5, | ||||||
|  |       "rot": "DOWN" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 3, | ||||||
|  |       "x": 5, | ||||||
|  |       "y": 6, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 2, | ||||||
|  |       "x": 4, | ||||||
|  |       "y": 4, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 2, | ||||||
|  |       "x": 7, | ||||||
|  |       "y": 4, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 2, | ||||||
|  |       "x": 4, | ||||||
|  |       "y": 2, | ||||||
|  |       "rot": "DOWN" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 1, | ||||||
|  |       "x": 6, | ||||||
|  |       "y": 2, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 1, | ||||||
|  |       "x": 8, | ||||||
|  |       "y": 2, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 1, | ||||||
|  |       "x": 6, | ||||||
|  |       "y": 0, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 1, | ||||||
|  |       "x": 8, | ||||||
|  |       "y": 0, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										66
									
								
								Projekte/battleship/client/maps/map2.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,66 @@ | |||||||
|  | { | ||||||
|  |   "width": 10, | ||||||
|  |   "height": 10, | ||||||
|  |   "ships": [ | ||||||
|  |     { | ||||||
|  |       "length": 4, | ||||||
|  |       "x": 0, | ||||||
|  |       "y": 5, | ||||||
|  |       "rot": "DOWN" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 3, | ||||||
|  |       "x": 0, | ||||||
|  |       "y": 9, | ||||||
|  |       "rot": "DOWN" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 3, | ||||||
|  |       "x": 2, | ||||||
|  |       "y": 6, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 2, | ||||||
|  |       "x": 4, | ||||||
|  |       "y": 8, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 2, | ||||||
|  |       "x": 2, | ||||||
|  |       "y": 4, | ||||||
|  |       "rot": "DOWN" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 2, | ||||||
|  |       "x": 2, | ||||||
|  |       "y": 1, | ||||||
|  |       "rot": "DOWN" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 1, | ||||||
|  |       "x": 6, | ||||||
|  |       "y": 2, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 1, | ||||||
|  |       "x": 8, | ||||||
|  |       "y": 2, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 1, | ||||||
|  |       "x": 6, | ||||||
|  |       "y": 0, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "length": 1, | ||||||
|  |       "x": 8, | ||||||
|  |       "y": 0, | ||||||
|  |       "rot": "RIGHT" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -0,0 +1,440 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client; | ||||||
|  |  | ||||||
|  | import com.jme3.app.DebugKeysAppState; | ||||||
|  | import com.jme3.app.SimpleApplication; | ||||||
|  | import com.jme3.app.StatsAppState; | ||||||
|  | import com.jme3.font.BitmapFont; | ||||||
|  | import com.jme3.font.BitmapText; | ||||||
|  | import com.jme3.input.KeyInput; | ||||||
|  | import com.jme3.input.MouseInput; | ||||||
|  | import com.jme3.input.controls.ActionListener; | ||||||
|  | import com.jme3.input.controls.KeyTrigger; | ||||||
|  | import com.jme3.input.controls.MouseButtonTrigger; | ||||||
|  | import com.jme3.system.AppSettings; | ||||||
|  | import com.simsilica.lemur.GuiGlobals; | ||||||
|  | import com.simsilica.lemur.style.BaseStyles; | ||||||
|  | import pp.battleship.client.gui.BattleAppState; | ||||||
|  | import pp.battleship.client.gui.EditorAppState; | ||||||
|  | import pp.battleship.client.gui.GameMusic; | ||||||
|  | import pp.battleship.client.gui.SeaAppState; | ||||||
|  | import pp.battleship.game.client.BattleshipClient; | ||||||
|  | import pp.battleship.game.client.ClientGameLogic; | ||||||
|  | import pp.battleship.game.client.ServerConnection; | ||||||
|  | import pp.battleship.game.singlemode.BattleshipClientConfig; | ||||||
|  | import pp.battleship.game.singlemode.ServerConnectionMockup; | ||||||
|  | import pp.battleship.notification.ClientStateEvent; | ||||||
|  | import pp.battleship.notification.GameEventListener; | ||||||
|  | import pp.battleship.notification.InfoTextEvent; | ||||||
|  | import pp.dialog.DialogBuilder; | ||||||
|  | import pp.dialog.DialogManager; | ||||||
|  | import pp.graphics.Draw; | ||||||
|  |  | ||||||
|  | import java.io.File; | ||||||
|  | import java.io.FileInputStream; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.lang.System.Logger; | ||||||
|  | import java.lang.System.Logger.Level; | ||||||
|  | import java.util.concurrent.ExecutorService; | ||||||
|  | import java.util.concurrent.Executors; | ||||||
|  | import java.util.logging.LogManager; | ||||||
|  |  | ||||||
|  | import static pp.battleship.Resources.lookup; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The main class for the Battleship client application. | ||||||
|  |  * It manages the initialization, input setup, GUI setup, and game states for the client. | ||||||
|  |  */ | ||||||
|  | public class BattleshipApp extends SimpleApplication implements BattleshipClient, GameEventListener { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Logger for logging messages within the application. | ||||||
|  |      */ | ||||||
|  |     private static final Logger LOGGER = System.getLogger(BattleshipApp.class.getName()); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Path to the styles script for GUI elements. | ||||||
|  |      */ | ||||||
|  |     private static final String STYLES_SCRIPT = "Interface/Lemur/pp-styles.groovy"; //NON-NLS | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Path to the font resource used in the GUI. | ||||||
|  |      */ | ||||||
|  |     private static final String FONT = "Interface/Fonts/Default.fnt"; //NON-NLS | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Path to the client configuration file, if one exists. | ||||||
|  |      */ | ||||||
|  |     private static final File CONFIG_FILE = new File("client.properties"); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Input mapping name for mouse clicks. | ||||||
|  |      */ | ||||||
|  |     public static final String CLICK = "CLICK"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Input mapping name for the Escape key. | ||||||
|  |      */ | ||||||
|  |     private static final String ESC = "ESC"; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Manager for handling dialogs within the application. | ||||||
|  |      */ | ||||||
|  |     private final DialogManager dialogManager = new DialogManager(this); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The server connection instance, used for communicating with the game server. | ||||||
|  |      */ | ||||||
|  |     private final ServerConnection serverConnection; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Instance of the {@link Draw} class for rendering graphics. | ||||||
|  |      */ | ||||||
|  |     private Draw draw; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Text display at the top of the GUI for showing information to the user. | ||||||
|  |      */ | ||||||
|  |     private BitmapText topText; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Executor service for handling asynchronous tasks within the application. | ||||||
|  |      */ | ||||||
|  |     private ExecutorService executor; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handler for managing the client's game logic. | ||||||
|  |      */ | ||||||
|  |     private final ClientGameLogic logic; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Configuration settings for the Battleship client application. | ||||||
|  |      */ | ||||||
|  |     private final BattleshipAppConfig config; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Listener for handling actions triggered by the Escape key. | ||||||
|  |      */ | ||||||
|  |     private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed); | ||||||
|  |  | ||||||
|  |     static { | ||||||
|  |         // Configure logging | ||||||
|  |         LogManager manager = LogManager.getLogManager(); | ||||||
|  |         try { | ||||||
|  |             manager.readConfiguration(new FileInputStream("logging.properties")); | ||||||
|  |             LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS | ||||||
|  |         } | ||||||
|  |         catch (IOException e) { | ||||||
|  |             LOGGER.log(Level.INFO, e.getMessage()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Starts the Battleship application. | ||||||
|  |      * | ||||||
|  |      * @param args Command-line arguments for launching the application. | ||||||
|  |      */ | ||||||
|  |     public static void main(String[] args) { | ||||||
|  |         new BattleshipApp().start(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a new {@code BattleshipApp} instance. | ||||||
|  |      * Initializes the configuration, server connection, and game logic listeners. | ||||||
|  |      */ | ||||||
|  |     private BattleshipApp() { | ||||||
|  |         config = new BattleshipAppConfig(); | ||||||
|  |         config.readFromIfExists(CONFIG_FILE); | ||||||
|  |         serverConnection = makeServerConnection(); | ||||||
|  |         logic = new ClientGameLogic(serverConnection); | ||||||
|  |         logic.addListener(this); | ||||||
|  |         setShowSettings(config.getShowSettings()); | ||||||
|  |         setSettings(makeSettings()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates and configures application settings from the client configuration. | ||||||
|  |      * | ||||||
|  |      * @return A configured {@link AppSettings} object. | ||||||
|  |      */ | ||||||
|  |     private AppSettings makeSettings() { | ||||||
|  |         final AppSettings settings = new AppSettings(true); | ||||||
|  |         settings.setTitle(lookup("battleship.name")); | ||||||
|  |         settings.setResolution(config.getResolutionWidth(), config.getResolutionHeight()); | ||||||
|  |         settings.setFullscreen(config.fullScreen()); | ||||||
|  |         settings.setUseRetinaFrameBuffer(config.useRetinaFrameBuffer()); | ||||||
|  |         settings.setGammaCorrection(config.useGammaCorrection()); | ||||||
|  |         return settings; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Factory method for creating a server connection based on the current | ||||||
|  |      * client configuration. | ||||||
|  |      * | ||||||
|  |      * @return A {@link ServerConnection} instance, which could be a real or mock server. | ||||||
|  |      */ | ||||||
|  |     private ServerConnection makeServerConnection() { | ||||||
|  |         if (config.isSingleMode()) | ||||||
|  |             return new ServerConnectionMockup(this); | ||||||
|  |         return new NetworkSupport(this); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the dialog manager responsible for managing in-game dialogs. | ||||||
|  |      * | ||||||
|  |      * @return The {@link DialogManager} instance. | ||||||
|  |      */ | ||||||
|  |     DialogManager getDialogManager() { | ||||||
|  |         return dialogManager; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the game logic handler for the client. | ||||||
|  |      * | ||||||
|  |      * @return The {@link ClientGameLogic} instance. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public ClientGameLogic getGameLogic() { | ||||||
|  |         return logic; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the current configuration settings for the Battleship client. | ||||||
|  |      * | ||||||
|  |      * @return The {@link BattleshipClientConfig} instance. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public BattleshipAppConfig getConfig() { | ||||||
|  |         return config; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initializes the application. | ||||||
|  |      * Sets up input mappings, GUI, game states, and connects to the server. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void simpleInitApp() { | ||||||
|  |         setPauseOnLostFocus(false); | ||||||
|  |         draw = new Draw(assetManager); | ||||||
|  |         setupInput(); | ||||||
|  |         setupStates(); | ||||||
|  |         setupGui(); | ||||||
|  |         serverConnection.connect(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sets up the graphical user interface (GUI) for the application. | ||||||
|  |      */ | ||||||
|  |     private void setupGui() { | ||||||
|  |         GuiGlobals.initialize(this); | ||||||
|  |         BaseStyles.loadStyleResources(STYLES_SCRIPT); | ||||||
|  |         GuiGlobals.getInstance().getStyles().setDefaultStyle("pp"); //NON-NLS | ||||||
|  |         final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS | ||||||
|  |         topText = new BitmapText(normalFont); | ||||||
|  |         final int height = context.getSettings().getHeight(); | ||||||
|  |         topText.setLocalTranslation(10f, height - 10f, 0f); | ||||||
|  |         topText.setColor(config.getTopColor()); | ||||||
|  |         guiNode.attachChild(topText); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Configures input mappings and sets up listeners for user interactions. | ||||||
|  |      */ | ||||||
|  |     private void setupInput() { | ||||||
|  |         inputManager.deleteMapping(INPUT_MAPPING_EXIT); | ||||||
|  |         inputManager.setCursorVisible(false); | ||||||
|  |         inputManager.addMapping(ESC, new KeyTrigger(KeyInput.KEY_ESCAPE)); | ||||||
|  |         inputManager.addMapping(CLICK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); | ||||||
|  |         inputManager.addListener(escapeListener, ESC); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initializes and attaches the necessary application states for the game. | ||||||
|  |      */ | ||||||
|  |     private void setupStates() { | ||||||
|  |         if (config.getShowStatistics()) { | ||||||
|  |             final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS | ||||||
|  |             final StatsAppState stats = new StatsAppState(guiNode, normalFont); | ||||||
|  |             stateManager.attach(stats); | ||||||
|  |         } | ||||||
|  |         flyCam.setEnabled(false); | ||||||
|  |         stateManager.detach(stateManager.getState(StatsAppState.class)); | ||||||
|  |         stateManager.detach(stateManager.getState(DebugKeysAppState.class)); | ||||||
|  |  | ||||||
|  |         attachGameSound(); | ||||||
|  |         attachGameMusic(); | ||||||
|  |         stateManager.attachAll(new EditorAppState(), new BattleAppState(), new SeaAppState()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Attaches the game sound state and sets its initial enabled state. | ||||||
|  |      */ | ||||||
|  |     private void attachGameSound() { | ||||||
|  |         final GameSound gameSound = new GameSound(); | ||||||
|  |         logic.addListener(gameSound); | ||||||
|  |         gameSound.setEnabled(GameSound.enabledInPreferences()); | ||||||
|  |         stateManager.attach(gameSound); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Attaches the background music state and sets its initial enabled state. | ||||||
|  |      */ | ||||||
|  |     private void attachGameMusic() { | ||||||
|  |         final GameMusic gameSound = new GameMusic(); | ||||||
|  |         gameSound.setEnabled(GameMusic.enabledInPreferences()); | ||||||
|  |         stateManager.attach(gameSound); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Updates the application state every frame. | ||||||
|  |      * This method is called once per frame during the game loop. | ||||||
|  |      * | ||||||
|  |      * @param tpf Time per frame in seconds. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void simpleUpdate(float tpf) { | ||||||
|  |         super.simpleUpdate(tpf); | ||||||
|  |         dialogManager.update(tpf); | ||||||
|  |         logic.update(tpf); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles the Escape key action to either close the top dialog or show the main menu. | ||||||
|  |      * | ||||||
|  |      * @param isPressed Indicates whether the Escape key is pressed. | ||||||
|  |      */ | ||||||
|  |     private void escape(boolean isPressed) { | ||||||
|  |         if (!isPressed) return; | ||||||
|  |         if (dialogManager.showsDialog()) | ||||||
|  |             dialogManager.escape(); | ||||||
|  |         else | ||||||
|  |             new Menu(this).open(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the {@link Draw} instance used for rendering graphical elements in the game. | ||||||
|  |      * | ||||||
|  |      * @return The {@link Draw} instance. | ||||||
|  |      */ | ||||||
|  |     public Draw getDraw() { | ||||||
|  |         return draw; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles a request to close the application. | ||||||
|  |      * If the request is initiated by pressing ESC, this parameter is true. | ||||||
|  |      * | ||||||
|  |      * @param esc If true, the request is due to the ESC key being pressed. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void requestClose(boolean esc) { /* do nothing */ } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Closes the application, displaying a confirmation dialog if the client is connected to a server. | ||||||
|  |      */ | ||||||
|  |     public void closeApp() { | ||||||
|  |         if (serverConnection.isConnected()) | ||||||
|  |             confirmDialog(lookup("confirm.leaving"), this::close); | ||||||
|  |         else | ||||||
|  |             close(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Closes the application, disconnecting from the server and stopping the application. | ||||||
|  |      */ | ||||||
|  |     private void close() { | ||||||
|  |         serverConnection.disconnect(); | ||||||
|  |         stop(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Updates the informational text displayed in the GUI. | ||||||
|  |      * | ||||||
|  |      * @param text The information text to display. | ||||||
|  |      */ | ||||||
|  |     void setInfoText(String text) { | ||||||
|  |         LOGGER.log(Level.DEBUG, "setInfoText {0}", text); //NON-NLS | ||||||
|  |         topText.setText(text); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Updates the informational text in the GUI based on the key received in an {@link InfoTextEvent}. | ||||||
|  |      * | ||||||
|  |      * @param event The {@link InfoTextEvent} containing the key for the text to display. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void receivedEvent(InfoTextEvent event) { | ||||||
|  |         LOGGER.log(Level.DEBUG, "received info text {0}", event.key()); //NON-NLS | ||||||
|  |         setInfoText(lookup(event.key())); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles client state events to update the game states accordingly. | ||||||
|  |      * | ||||||
|  |      * @param event The {@link ClientStateEvent} representing the state change. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void receivedEvent(ClientStateEvent event) { | ||||||
|  |         stateManager.getState(EditorAppState.class).setEnabled(logic.showEditor()); | ||||||
|  |         stateManager.getState(BattleAppState.class).setEnabled(logic.showBattle()); | ||||||
|  |         stateManager.getState(SeaAppState.class).setEnabled(logic.showBattle()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the executor service used for handling multithreaded tasks. | ||||||
|  |      * | ||||||
|  |      * @return The {@link ExecutorService} instance. | ||||||
|  |      */ | ||||||
|  |     public ExecutorService getExecutor() { | ||||||
|  |         if (executor == null) | ||||||
|  |             executor = Executors.newCachedThreadPool(); | ||||||
|  |         return executor; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Stops the application, shutting down the executor service and halting execution. | ||||||
|  |      * | ||||||
|  |      * @param waitFor If true, waits for the application to stop before returning. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void stop(boolean waitFor) { | ||||||
|  |         if (executor != null) executor.shutdownNow(); | ||||||
|  |         super.stop(waitFor); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Displays a confirmation dialog with a specified question and action for the "Yes" button. | ||||||
|  |      * | ||||||
|  |      * @param question  The question to display in the dialog. | ||||||
|  |      * @param yesAction The action to perform if "Yes" is selected. | ||||||
|  |      */ | ||||||
|  |     void confirmDialog(String question, Runnable yesAction) { | ||||||
|  |         DialogBuilder.simple(dialogManager) | ||||||
|  |                      .setTitle(lookup("dialog.question")) | ||||||
|  |                      .setText(question) | ||||||
|  |                      .setOkButton(lookup("button.yes"), yesAction) | ||||||
|  |                      .setNoButton(lookup("button.no")) | ||||||
|  |                      .build() | ||||||
|  |                      .open(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Displays an error dialog with the specified error message. | ||||||
|  |      * | ||||||
|  |      * @param errorMessage The error message to display in the dialog. | ||||||
|  |      */ | ||||||
|  |     void errorDialog(String errorMessage) { | ||||||
|  |         DialogBuilder.simple(dialogManager) | ||||||
|  |                      .setTitle(lookup("dialog.error")) | ||||||
|  |                      .setText(errorMessage) | ||||||
|  |                      .setOkButton(lookup("button.ok")) | ||||||
|  |                      .build() | ||||||
|  |                      .open(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,196 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client; | ||||||
|  |  | ||||||
|  | import com.jme3.math.ColorRGBA; | ||||||
|  | import pp.battleship.game.singlemode.BattleshipClientConfig; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Provides access to the Battleship application configuration. | ||||||
|  |  * Extends {@link BattleshipClientConfig} to include additional properties specific to the client, | ||||||
|  |  * particularly those related to screen settings and visual customization. | ||||||
|  |  * <p> | ||||||
|  |  * <b>Note:</b> Attributes of this class should not be marked as {@code final} | ||||||
|  |  * to ensure proper functionality when reading from a properties file. | ||||||
|  |  * </p> | ||||||
|  |  */ | ||||||
|  | public class BattleshipAppConfig extends BattleshipClientConfig { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Converts a string value found in the properties file into an object of the specified type. | ||||||
|  |      * Extends the superclass method to support conversion to {@link ColorRGBA}. | ||||||
|  |      * | ||||||
|  |      * @param value      the string value to be converted | ||||||
|  |      * @param targetType the target type into which the value string is converted | ||||||
|  |      * @return the converted object of the specified type | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected Object convertToType(String value, Class<?> targetType) { | ||||||
|  |         if (targetType == ColorRGBA.class) | ||||||
|  |             return makeColorRGBA(value); | ||||||
|  |         return super.convertToType(value, targetType); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Converts the specified string value to a corresponding {@link ColorRGBA} object. | ||||||
|  |      * | ||||||
|  |      * @param value the color in the format "red, green, blue, alpha" with all values in the range [0..1] | ||||||
|  |      * @return a {@link ColorRGBA} object representing the color | ||||||
|  |      * @throws IllegalArgumentException if the input string is not in the expected format | ||||||
|  |      */ | ||||||
|  |     private static ColorRGBA makeColorRGBA(String value) { | ||||||
|  |         String[] split = value.split(",", -1); | ||||||
|  |         try { | ||||||
|  |             if (split.length == 4) | ||||||
|  |                 return new ColorRGBA(Float.parseFloat(split[0]), | ||||||
|  |                                      Float.parseFloat(split[1]), | ||||||
|  |                                      Float.parseFloat(split[2]), | ||||||
|  |                                      Float.parseFloat(split[3])); | ||||||
|  |         } | ||||||
|  |         catch (NumberFormatException e) { | ||||||
|  |             // deliberately left empty | ||||||
|  |         } | ||||||
|  |         throw new IllegalArgumentException(value + " should consist of exactly 4 numbers"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The width of the game view resolution in pixels. | ||||||
|  |      */ | ||||||
|  |     @Property("settings.resolution.width") //NON-NLS | ||||||
|  |     private int resolutionWidth = 1200; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The height of the game view resolution in pixels. | ||||||
|  |      */ | ||||||
|  |     @Property("settings.resolution.height") //NON-NLS | ||||||
|  |     private int resolutionHeight = 800; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Specifies whether the game should start in full-screen mode. | ||||||
|  |      */ | ||||||
|  |     @Property("settings.full-screen") //NON-NLS | ||||||
|  |     private boolean fullScreen = false; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Specifies whether gamma correction should be enabled. | ||||||
|  |      * If enabled, the main framebuffer is configured for sRGB colors, | ||||||
|  |      * and sRGB images are linearized. | ||||||
|  |      * <p> | ||||||
|  |      * Requires a GPU that supports GL_ARB_framebuffer_sRGB; otherwise, this setting will be ignored. | ||||||
|  |      * </p> | ||||||
|  |      */ | ||||||
|  |     @Property("settings.use-gamma-correction") //NON-NLS | ||||||
|  |     private boolean useGammaCorrection = true; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Specifies whether full resolution framebuffers should be used on Retina displays. | ||||||
|  |      * This setting is ignored on non-Retina platforms. | ||||||
|  |      */ | ||||||
|  |     @Property("settings.use-retina-framebuffer") //NON-NLS | ||||||
|  |     private boolean useRetinaFrameBuffer = false; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Specifies whether the settings window should be shown for configuring the game. | ||||||
|  |      */ | ||||||
|  |     @Property("settings.show") //NON-NLS | ||||||
|  |     private boolean showSettings = false; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Specifies whether the JME statistics window should be shown in the lower left corner of the screen. | ||||||
|  |      */ | ||||||
|  |     @Property("statistics.show") //NON-NLS | ||||||
|  |     private boolean showStatistics = false; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The color of the top text during gameplay, represented as a {@link ColorRGBA} object. | ||||||
|  |      */ | ||||||
|  |     @Property("overlay.top.color") //NON-NLS | ||||||
|  |     private ColorRGBA topColor = ColorRGBA.White; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a default {@code BattleshipAppConfig} with predefined values. | ||||||
|  |      */ | ||||||
|  |     public BattleshipAppConfig() { | ||||||
|  |         // Default constructor | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the width of the game view resolution in pixels. | ||||||
|  |      * | ||||||
|  |      * @return the width of the game view resolution in pixels | ||||||
|  |      */ | ||||||
|  |     public int getResolutionWidth() { | ||||||
|  |         return resolutionWidth; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the height of the game view resolution in pixels. | ||||||
|  |      * | ||||||
|  |      * @return the height of the game view resolution in pixels | ||||||
|  |      */ | ||||||
|  |     public int getResolutionHeight() { | ||||||
|  |         return resolutionHeight; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns whether the game should start in full-screen mode. | ||||||
|  |      * | ||||||
|  |      * @return {@code true} if the game should start in full-screen mode; {@code false} otherwise | ||||||
|  |      */ | ||||||
|  |     public boolean fullScreen() { | ||||||
|  |         return fullScreen; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns whether gamma correction is enabled. | ||||||
|  |      * If enabled, the main framebuffer is configured for sRGB colors, | ||||||
|  |      * and sRGB images are linearized. | ||||||
|  |      * | ||||||
|  |      * @return {@code true} if gamma correction is enabled; {@code false} otherwise | ||||||
|  |      */ | ||||||
|  |     public boolean useGammaCorrection() { | ||||||
|  |         return useGammaCorrection; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns whether full resolution framebuffers should be used on Retina displays. | ||||||
|  |      * This setting is ignored on non-Retina platforms. | ||||||
|  |      * | ||||||
|  |      * @return {@code true} if full resolution framebuffers should be used on Retina displays; {@code false} otherwise | ||||||
|  |      */ | ||||||
|  |     public boolean useRetinaFrameBuffer() { | ||||||
|  |         return useRetinaFrameBuffer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns whether the settings window should be shown for configuring the game. | ||||||
|  |      * | ||||||
|  |      * @return {@code true} if the settings window should be shown; {@code false} otherwise | ||||||
|  |      */ | ||||||
|  |     public boolean getShowSettings() { | ||||||
|  |         return showSettings; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns whether the JME statistics window should be shown in the lower left corner of the screen. | ||||||
|  |      * | ||||||
|  |      * @return {@code true} if the statistics window should be shown; {@code false} otherwise | ||||||
|  |      */ | ||||||
|  |     public boolean getShowStatistics() { | ||||||
|  |         return showStatistics; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the color of the top text during gameplay as a {@link ColorRGBA} object. | ||||||
|  |      * | ||||||
|  |      * @return the color of the top text during gameplay | ||||||
|  |      */ | ||||||
|  |     public ColorRGBA getTopColor() { | ||||||
|  |         return topColor; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,102 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client; | ||||||
|  |  | ||||||
|  | import com.jme3.app.Application; | ||||||
|  | import com.jme3.app.state.AbstractAppState; | ||||||
|  | import com.jme3.app.state.AppStateManager; | ||||||
|  | import pp.battleship.game.client.ClientGameLogic; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Abstract class representing a state in the Battleship game. | ||||||
|  |  * Extends the AbstractAppState from jMonkeyEngine to manage state behavior. | ||||||
|  |  */ | ||||||
|  | public abstract class BattleshipAppState extends AbstractAppState { | ||||||
|  |     private BattleshipApp app; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a new BattleshipAppState that is initially disabled. | ||||||
|  |      * | ||||||
|  |      * @see #setEnabled(boolean) | ||||||
|  |      */ | ||||||
|  |     protected BattleshipAppState() { | ||||||
|  |         setEnabled(false); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initializes the state manager and application. | ||||||
|  |      * | ||||||
|  |      * @param stateManager The state manager | ||||||
|  |      * @param application  The application instance | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void initialize(AppStateManager stateManager, Application application) { | ||||||
|  |         super.initialize(stateManager, application); | ||||||
|  |         this.app = (BattleshipApp) application; | ||||||
|  |         if (isEnabled()) enableState(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the BattleshipApp instance associated with this BattleshipAppState. | ||||||
|  |      * | ||||||
|  |      * @return The BattleshipApp instance. | ||||||
|  |      */ | ||||||
|  |     public BattleshipApp getApp() { | ||||||
|  |         return app; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the client game logic handler. | ||||||
|  |      * | ||||||
|  |      * @return the client game logic handler | ||||||
|  |      */ | ||||||
|  |     public ClientGameLogic getGameLogic() { | ||||||
|  |         return app.getGameLogic(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Checks if any dialog is currently displayed. | ||||||
|  |      * | ||||||
|  |      * @return true if any dialog is currently shown, false otherwise | ||||||
|  |      */ | ||||||
|  |     public boolean showsDialog() { | ||||||
|  |         return app.getDialogManager().showsDialog(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sets the enabled state of the BattleshipAppState. | ||||||
|  |      * If the new state is the same as the current state, the method returns. | ||||||
|  |      * | ||||||
|  |      * @param enabled The new enabled state. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void setEnabled(boolean enabled) { | ||||||
|  |         if (isEnabled() == enabled) return; | ||||||
|  |         super.setEnabled(enabled); | ||||||
|  |         if (app != null) { | ||||||
|  |             if (enabled) | ||||||
|  |                 enableState(); | ||||||
|  |             else | ||||||
|  |                 disableState(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This method is called when the state is enabled. | ||||||
|  |      * It is meant to be overridden by subclasses to perform | ||||||
|  |      * specific actions when the state is enabled. | ||||||
|  |      */ | ||||||
|  |     protected abstract void enableState(); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This method is called when the state is disabled. | ||||||
|  |      * It is meant to be overridden by subclasses to perform | ||||||
|  |      * specific actions when the state is disabled. | ||||||
|  |      */ | ||||||
|  |     protected abstract void disableState(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,152 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client; | ||||||
|  |  | ||||||
|  | import com.jme3.app.Application; | ||||||
|  | import com.jme3.app.state.AbstractAppState; | ||||||
|  | import com.jme3.app.state.AppStateManager; | ||||||
|  | import com.jme3.asset.AssetLoadException; | ||||||
|  | import com.jme3.asset.AssetNotFoundException; | ||||||
|  | import com.jme3.audio.AudioData; | ||||||
|  | import com.jme3.audio.AudioNode; | ||||||
|  | import pp.battleship.notification.GameEventListener; | ||||||
|  | import pp.battleship.notification.SoundEvent; | ||||||
|  |  | ||||||
|  | import java.lang.System.Logger; | ||||||
|  | import java.lang.System.Logger.Level; | ||||||
|  | import java.util.prefs.Preferences; | ||||||
|  |  | ||||||
|  | import static pp.util.PreferencesUtils.getPreferences; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * An application state that plays sounds. | ||||||
|  |  */ | ||||||
|  | public class GameSound extends AbstractAppState implements GameEventListener { | ||||||
|  |     private static final Logger LOGGER = System.getLogger(GameSound.class.getName()); | ||||||
|  |     private static final Preferences PREFERENCES = getPreferences(GameSound.class); | ||||||
|  |     private static final String ENABLED_PREF = "enabled"; //NON-NLS | ||||||
|  |  | ||||||
|  |     private AudioNode splashSound; | ||||||
|  |     private AudioNode shipDestroyedSound; | ||||||
|  |     private AudioNode explosionSound; | ||||||
|  |     private AudioNode shellFlyingSound; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Checks if sound is enabled in the preferences. | ||||||
|  |      * | ||||||
|  |      * @return {@code true} if sound is enabled, {@code false} otherwise. | ||||||
|  |      */ | ||||||
|  |     public static boolean enabledInPreferences() { | ||||||
|  |         return PREFERENCES.getBoolean(ENABLED_PREF, true); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Toggles the game sound on or off. | ||||||
|  |      */ | ||||||
|  |     public void toggleSound() { | ||||||
|  |         setEnabled(!isEnabled()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sets the enabled state of this AppState. | ||||||
|  |      * Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)} | ||||||
|  |      * | ||||||
|  |      * @param enabled {@code true} to enable the AppState, {@code false} to disable it. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void setEnabled(boolean enabled) { | ||||||
|  |         if (isEnabled() == enabled) return; | ||||||
|  |         super.setEnabled(enabled); | ||||||
|  |         LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS | ||||||
|  |         PREFERENCES.putBoolean(ENABLED_PREF, enabled); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initializes the sound effects for the game. | ||||||
|  |      * Overrides {@link AbstractAppState#initialize(AppStateManager, Application)} | ||||||
|  |      * | ||||||
|  |      * @param stateManager The state manager | ||||||
|  |      * @param app          The application | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void initialize(AppStateManager stateManager, Application app) { | ||||||
|  |         super.initialize(stateManager, app); | ||||||
|  |         shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS | ||||||
|  |         splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS | ||||||
|  |         explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS | ||||||
|  |         shellFlyingSound = loadSound(app, "Sound/Effects/shell_flying.wav"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Loads a sound from the specified file. | ||||||
|  |      * | ||||||
|  |      * @param app  The application | ||||||
|  |      * @param name The name of the sound file. | ||||||
|  |      * @return The loaded AudioNode. | ||||||
|  |      */ | ||||||
|  |     private AudioNode loadSound(Application app, String name) { | ||||||
|  |         try { | ||||||
|  |             final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer); | ||||||
|  |             sound.setLooping(false); | ||||||
|  |             sound.setPositional(false); | ||||||
|  |             return sound; | ||||||
|  |         } | ||||||
|  |         catch (AssetLoadException | AssetNotFoundException ex) { | ||||||
|  |             LOGGER.log(Level.ERROR, ex.getMessage(), ex); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Plays the splash sound effect. | ||||||
|  |      */ | ||||||
|  |     public void splash() { | ||||||
|  |         if (isEnabled() && splashSound != null) | ||||||
|  |             splashSound.playInstance(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Plays the explosion sound effect. | ||||||
|  |      */ | ||||||
|  |     public void explosion() { | ||||||
|  |         if (isEnabled() && explosionSound != null) | ||||||
|  |             explosionSound.playInstance(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Plays sound effect when a ship has been destroyed. | ||||||
|  |      */ | ||||||
|  |     public void shipDestroyed() { | ||||||
|  |         if (isEnabled() && shipDestroyedSound != null) | ||||||
|  |             shipDestroyedSound.playInstance(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Plays the shell flying sound effect. | ||||||
|  |      */ | ||||||
|  |     public void shellFly() { | ||||||
|  |         if (isEnabled() && shellFlyingSound != null) { | ||||||
|  |             shellFlyingSound.playInstance(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles a recieved {@code SoundEvent} and plays the according sound. | ||||||
|  |      *  | ||||||
|  |      * @param event the Sound event to be processed | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void receivedEvent(SoundEvent event) { | ||||||
|  |         switch (event.sound()) { | ||||||
|  |             case EXPLOSION -> explosion(); | ||||||
|  |             case SPLASH -> splash(); | ||||||
|  |             case DESTROYED_SHIP -> shipDestroyed(); | ||||||
|  |             case SHELL_FLYING -> shellFly(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,154 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client; | ||||||
|  |  | ||||||
|  | import java.io.File; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.prefs.Preferences; | ||||||
|  |  | ||||||
|  | import com.simsilica.lemur.Button; | ||||||
|  | import com.simsilica.lemur.Checkbox; | ||||||
|  | import com.simsilica.lemur.Label; | ||||||
|  | import com.simsilica.lemur.style.ElementId; | ||||||
|  |  | ||||||
|  | import static pp.battleship.Resources.lookup; | ||||||
|  | import pp.battleship.client.gui.GameMusic; | ||||||
|  | import pp.dialog.Dialog; | ||||||
|  | import pp.dialog.StateCheckboxModel; | ||||||
|  | import pp.dialog.TextInputDialog; | ||||||
|  | import pp.battleship.client.gui.VolumeSlider; | ||||||
|  |  | ||||||
|  | import static pp.util.PreferencesUtils.getPreferences; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The Menu class represents the main menu in the Battleship game application. | ||||||
|  |  * It extends the Dialog class and provides functionalities for loading, saving, | ||||||
|  |  * returning to the game, and quitting the application. | ||||||
|  |  */ | ||||||
|  | class Menu extends Dialog { | ||||||
|  |     private static final Preferences PREFERENCES = getPreferences(Menu.class); | ||||||
|  |     private static final String LAST_PATH = "last.file.path"; | ||||||
|  |     private final BattleshipApp app; | ||||||
|  |     private final Button loadButton = new Button(lookup("menu.map.load")); | ||||||
|  |     private final Button saveButton = new Button(lookup("menu.map.save")); | ||||||
|  |     private final VolumeSlider slider; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs the Menu dialog for the Battleship application.+ | ||||||
|  |      * | ||||||
|  |      * @param app the BattleshipApp instance | ||||||
|  |      */ | ||||||
|  |     public Menu(BattleshipApp app) { | ||||||
|  |         super(app.getDialogManager()); | ||||||
|  |         this.app = app; | ||||||
|  |         slider = new VolumeSlider(app.getStateManager().getState(GameMusic.class)); | ||||||
|  |         addChild(new Label(lookup("battleship.name"), new ElementId("header"))); //NON-NLS | ||||||
|  |         addChild(new Checkbox(lookup("menu.sound-enabled"), new StateCheckboxModel(app, GameSound.class))); | ||||||
|  |  | ||||||
|  |         addChild(new Checkbox(lookup("menu.background-sound-enabled"), new StateCheckboxModel(app, GameMusic.class))); | ||||||
|  |          | ||||||
|  |         addChild(slider); | ||||||
|  |  | ||||||
|  |         addChild(loadButton).addClickCommands(s -> ifTopDialog(this::loadDialog)); | ||||||
|  |         addChild(saveButton).addClickCommands(s -> ifTopDialog(this::saveDialog)); | ||||||
|  |         addChild(new Button(lookup("menu.return-to-game"))).addClickCommands(s -> ifTopDialog(this::close)); | ||||||
|  |         addChild(new Button(lookup("menu.quit"))).addClickCommands(s -> ifTopDialog(app::closeApp)); | ||||||
|  |          | ||||||
|  |         update(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Updates the state of the load and save buttons based on the game logic. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void update() { | ||||||
|  |         loadButton.setEnabled(app.getGameLogic().mayLoadMap()); | ||||||
|  |         saveButton.setEnabled(app.getGameLogic().maySaveMap()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void update(float delta) { | ||||||
|  |         slider.update(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * As an escape action, this method closes the menu if it is the top dialog. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void escape() { | ||||||
|  |         close(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Functional interface for file actions. | ||||||
|  |      */ | ||||||
|  |     @FunctionalInterface | ||||||
|  |     private interface FileAction { | ||||||
|  |         /** | ||||||
|  |          * Executes a file action. | ||||||
|  |          * | ||||||
|  |          * @param file the file to be processed | ||||||
|  |          * @throws IOException if an I/O error occurs | ||||||
|  |          */ | ||||||
|  |         void run(File file) throws IOException; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles the file action for the provided dialog. | ||||||
|  |      * | ||||||
|  |      * @param fileAction the file action to be executed | ||||||
|  |      * @param dialog     the dialog providing the file input | ||||||
|  |      */ | ||||||
|  |     private void handle(FileAction fileAction, TextInputDialog dialog) { | ||||||
|  |         try { | ||||||
|  |             final String path = dialog.getInput().getText(); | ||||||
|  |             PREFERENCES.put(LAST_PATH, path); | ||||||
|  |             fileAction.run(new File(path)); | ||||||
|  |             dialog.close(); | ||||||
|  |         } | ||||||
|  |         catch (IOException e) { | ||||||
|  |             app.errorDialog(e.getLocalizedMessage()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Shows a file dialog for loading or saving files. | ||||||
|  |      * | ||||||
|  |      * @param fileAction the action to perform with the selected file | ||||||
|  |      * @param label      the label for the dialog | ||||||
|  |      */ | ||||||
|  |     private void fileDialog(FileAction fileAction, String label) { | ||||||
|  |         final TextInputDialog dialog = | ||||||
|  |                 TextInputDialog.builder(app.getDialogManager()) | ||||||
|  |                                .setLabel(lookup("label.file")) | ||||||
|  |                                .setFocus(TextInputDialog::getInput) | ||||||
|  |                                .setTitle(label) | ||||||
|  |                                .setOkButton(lookup("button.ok"), d -> handle(fileAction, d)) | ||||||
|  |                                .setNoButton(lookup("button.cancel")) | ||||||
|  |                                .setOkClose(false) | ||||||
|  |                                .build(); | ||||||
|  |         final String path = PREFERENCES.get(LAST_PATH, null); | ||||||
|  |         if (path != null) | ||||||
|  |             dialog.getInput().setText(path.trim()); | ||||||
|  |         dialog.open(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Shows the load dialog for loading maps. | ||||||
|  |      */ | ||||||
|  |     private void loadDialog() { | ||||||
|  |         fileDialog(app.getGameLogic()::loadMap, lookup("menu.map.load")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Shows the save dialog for saving maps. | ||||||
|  |      */ | ||||||
|  |     private void saveDialog() { | ||||||
|  |         fileDialog(app.getGameLogic()::saveMap, lookup("menu.map.save")); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,177 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client; | ||||||
|  |  | ||||||
|  | import java.lang.System.Logger; | ||||||
|  | import java.lang.System.Logger.Level; | ||||||
|  | import java.util.concurrent.ExecutionException; | ||||||
|  | import java.util.concurrent.Future; | ||||||
|  |  | ||||||
|  | import com.simsilica.lemur.Button; | ||||||
|  | import com.simsilica.lemur.Container; | ||||||
|  | import com.simsilica.lemur.Label; | ||||||
|  | import com.simsilica.lemur.TextField; | ||||||
|  | import com.simsilica.lemur.component.SpringGridLayout; | ||||||
|  |  | ||||||
|  | import static pp.battleship.Resources.lookup; | ||||||
|  | import pp.battleship.server.BattleshipServer; | ||||||
|  | import pp.dialog.Dialog; | ||||||
|  | import pp.dialog.DialogBuilder; | ||||||
|  | import pp.dialog.SimpleDialog; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Represents a dialog for setting up a network connection in the Battleship game. | ||||||
|  |  * Allows users to specify the host and port for connecting to a game server. | ||||||
|  |  */ | ||||||
|  | class NetworkDialog extends SimpleDialog { | ||||||
|  |     private static final Logger LOGGER = System.getLogger(NetworkDialog.class.getName()); | ||||||
|  |     private static final String LOCALHOST = "localhost"; //NON-NLS | ||||||
|  |     private static final String DEFAULT_PORT = "42069"; //NON-NLS | ||||||
|  |     private final NetworkSupport network; | ||||||
|  |     private final TextField host = new TextField(LOCALHOST); | ||||||
|  |     private final TextField port = new TextField(DEFAULT_PORT); | ||||||
|  |     // private final Button serverButton = new Button(lookup("client.server-star")); | ||||||
|  |     private final Button serverButton = new Button(lookup("client.server-start")); | ||||||
|  |     private String hostname; | ||||||
|  |     private int portNumber; | ||||||
|  |     private Future<Object> connectionFuture; | ||||||
|  |     private Dialog progressDialog; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a new NetworkDialog. | ||||||
|  |      * | ||||||
|  |      * @param network The NetworkSupport instance to be used for network operations. | ||||||
|  |      */ | ||||||
|  |     NetworkDialog(NetworkSupport network) { | ||||||
|  |         super(network.getApp().getDialogManager()); | ||||||
|  |         this.network = network; | ||||||
|  |         host.setSingleLine(true); | ||||||
|  |         host.setPreferredWidth(400f); | ||||||
|  |         port.setSingleLine(true); | ||||||
|  |  | ||||||
|  |         final BattleshipApp app = network.getApp(); | ||||||
|  |         final Container input = new Container(new SpringGridLayout()); | ||||||
|  |         input.addChild(new Label(lookup("host.name") + ":  ")); | ||||||
|  |         input.addChild(host, 1); | ||||||
|  |         input.addChild(new Label(lookup("port.number") + ":  ")); | ||||||
|  |         input.addChild(port, 1); | ||||||
|  |  | ||||||
|  |         DialogBuilder.simple(app.getDialogManager()) | ||||||
|  |                      .setTitle(lookup("server.dialog")) | ||||||
|  |                      .setExtension(d -> d.addChild(input)) | ||||||
|  |                      .setOkButton(lookup("button.connect"), d -> connect()) | ||||||
|  |                      .setNoButton(lookup("button.cancel"), app::closeApp) | ||||||
|  |                      .setOkClose(false) | ||||||
|  |                      .setNoClose(false) | ||||||
|  |                      .build(this); | ||||||
|  |          | ||||||
|  |         //Add the button to start the sever | ||||||
|  |         addChild(serverButton).addClickCommands(s -> ifTopDialog(this::startServerInThread)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles the action for the connect button in the connection dialog. | ||||||
|  |      * Tries to parse the port number and initiate connection to the server. | ||||||
|  |      */ | ||||||
|  |     private void connect() { | ||||||
|  |         LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS | ||||||
|  |         try { | ||||||
|  |             hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText(); | ||||||
|  |             portNumber = Integer.parseInt(port.getText()); | ||||||
|  |             openProgressDialog(); | ||||||
|  |             connectionFuture = network.getApp().getExecutor().submit(this::initNetwork); | ||||||
|  |         } | ||||||
|  |         catch (NumberFormatException e) { | ||||||
|  |             network.getApp().errorDialog(lookup("port.must.be.integer")); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a dialog indicating that the connection is in progress. | ||||||
|  |      */ | ||||||
|  |     private void openProgressDialog() { | ||||||
|  |         progressDialog = DialogBuilder.simple(network.getApp().getDialogManager()) | ||||||
|  |                                       .setText(lookup("label.connecting")) | ||||||
|  |                                       .build(); | ||||||
|  |         progressDialog.open(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Tries to initialize the network connection. | ||||||
|  |      * | ||||||
|  |      * @throws RuntimeException If an error occurs when creating the client. | ||||||
|  |      */ | ||||||
|  |     private Object initNetwork() { | ||||||
|  |         try { | ||||||
|  |             network.initNetwork(hostname, portNumber); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         catch (Exception e) { | ||||||
|  |             throw new RuntimeException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This method is called by {@linkplain pp.dialog.DialogManager#update(float)} for periodically | ||||||
|  |      * updating this dialog. T | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void update(float delta) { | ||||||
|  |         if (connectionFuture != null && connectionFuture.isDone()) | ||||||
|  |             try { | ||||||
|  |                 connectionFuture.get(); | ||||||
|  |                 success(); | ||||||
|  |             } | ||||||
|  |             catch (ExecutionException e) { | ||||||
|  |                 failure(e.getCause()); | ||||||
|  |             } | ||||||
|  |             catch (InterruptedException e) { | ||||||
|  |                 LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS | ||||||
|  |                 Thread.currentThread().interrupt(); | ||||||
|  |             } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles a successful connection to the game server. | ||||||
|  |      */ | ||||||
|  |     private void success() { | ||||||
|  |         connectionFuture = null; | ||||||
|  |         progressDialog.close(); | ||||||
|  |         this.close(); | ||||||
|  |         network.getApp().setInfoText(lookup("wait.for.an.opponent")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles a failed connection attempt. | ||||||
|  |      * | ||||||
|  |      * @param e The cause of the failure. | ||||||
|  |      */ | ||||||
|  |     private void failure(Throwable e) { | ||||||
|  |         connectionFuture = null; | ||||||
|  |         progressDialog.close(); | ||||||
|  |         network.getApp().errorDialog(lookup("server.connection.failed")); | ||||||
|  |         network.getApp().setInfoText(e.getLocalizedMessage()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Starts the server in a separate thread. | ||||||
|  |      */ | ||||||
|  |     private void startServerInThread() { | ||||||
|  |         serverButton.setEnabled(false); | ||||||
|  |         Thread serverThread = new Thread(() -> { | ||||||
|  |             try { | ||||||
|  |                 BattleshipServer.main(null); | ||||||
|  |             } catch (Exception e) { | ||||||
|  |                 serverButton.setEnabled(true); | ||||||
|  |                 LOGGER.log(Level.ERROR, "Server could not be started", e); | ||||||
|  |                 network.getApp().errorDialog("Could not start server: " + e.getMessage()); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         serverThread.start(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,152 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client; | ||||||
|  |  | ||||||
|  | import com.jme3.network.Client; | ||||||
|  | import com.jme3.network.ClientStateListener; | ||||||
|  | import com.jme3.network.Message; | ||||||
|  | import com.jme3.network.MessageListener; | ||||||
|  | import com.jme3.network.Network; | ||||||
|  | import pp.battleship.game.client.ServerConnection; | ||||||
|  | import pp.battleship.message.client.ClientMessage; | ||||||
|  | import pp.battleship.message.server.ServerMessage; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.lang.System.Logger; | ||||||
|  | import java.lang.System.Logger.Level; | ||||||
|  |  | ||||||
|  | import static pp.battleship.Resources.lookup; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Manages the network connection for the Battleship application. | ||||||
|  |  * Handles connecting to and disconnecting from the server, and sending messages. | ||||||
|  |  */ | ||||||
|  | class NetworkSupport implements MessageListener<Client>, ClientStateListener, ServerConnection { | ||||||
|  |     private static final Logger LOGGER = System.getLogger(NetworkSupport.class.getName()); | ||||||
|  |     private final BattleshipApp app; | ||||||
|  |     private Client client; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a NetworkSupport instance for the given Battleship application. | ||||||
|  |      * | ||||||
|  |      * @param app The Battleship application instance. | ||||||
|  |      */ | ||||||
|  |     public NetworkSupport(BattleshipApp app) { | ||||||
|  |         this.app = app; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the Battleship application instance. | ||||||
|  |      * | ||||||
|  |      * @return Battleship application instance | ||||||
|  |      */ | ||||||
|  |     BattleshipApp getApp() { | ||||||
|  |         return app; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Checks if there is a connection to the game server. | ||||||
|  |      * | ||||||
|  |      * @return true if there is a connection to the game server, false otherwise. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public boolean isConnected() { | ||||||
|  |         return client != null && client.isConnected(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Attempts to join the game if there is no connection yet. | ||||||
|  |      * Opens a dialog for the user to enter the host and port information. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void connect() { | ||||||
|  |         if (client == null) | ||||||
|  |             new NetworkDialog(this).open(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Closes the client connection. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void disconnect() { | ||||||
|  |         if (client == null) return; | ||||||
|  |         client.close(); | ||||||
|  |         client = null; | ||||||
|  |         LOGGER.log(Level.INFO, "client closed"); //NON-NLS | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initializes the network connection. | ||||||
|  |      * | ||||||
|  |      * @param host The server's address. | ||||||
|  |      * @param port The server's port. | ||||||
|  |      * @throws IOException If an I/O error occurs when creating the client. | ||||||
|  |      */ | ||||||
|  |     void initNetwork(String host, int port) throws IOException { | ||||||
|  |         if (client != null) | ||||||
|  |             throw new IllegalStateException("trying to join a game again"); | ||||||
|  |         client = Network.connectToServer(host, port); | ||||||
|  |         client.start(); | ||||||
|  |         client.addMessageListener(this); | ||||||
|  |         client.addClientStateListener(this); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Called when a message is received from the server. | ||||||
|  |      * | ||||||
|  |      * @param client  The client instance that received the message. | ||||||
|  |      * @param message The message received from the server. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void messageReceived(Client client, Message message) { | ||||||
|  |         LOGGER.log(Level.INFO, "message received from server: {0}", message); //NON-NLS | ||||||
|  |         if (message instanceof ServerMessage serverMessage) | ||||||
|  |             app.enqueue(() -> serverMessage.accept(app.getGameLogic())); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Called when the client has successfully connected to the server. | ||||||
|  |      * | ||||||
|  |      * @param client The client that connected to the server. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void clientConnected(Client client) { | ||||||
|  |         LOGGER.log(Level.INFO, "Client connected: {0}", client); //NON-NLS | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Called when the client is disconnected from the server. | ||||||
|  |      * | ||||||
|  |      * @param client         The client that was disconnected. | ||||||
|  |      * @param disconnectInfo Information about the disconnection. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void clientDisconnected(Client client, DisconnectInfo disconnectInfo) { | ||||||
|  |         LOGGER.log(Level.INFO, "Client {0} disconnected: {1}", client, disconnectInfo); //NON-NLS | ||||||
|  |         if (this.client != client) | ||||||
|  |             throw new IllegalArgumentException("parameter value must be client"); | ||||||
|  |         LOGGER.log(Level.INFO, "client still connected: {0}", client.isConnected()); //NON-NLS | ||||||
|  |         this.client = null; | ||||||
|  |         disconnect(); | ||||||
|  |         app.enqueue(() -> app.setInfoText(lookup("lost.connection.to.server"))); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sends the specified message to the server. | ||||||
|  |      * | ||||||
|  |      * @param message The message to be sent to the server. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void send(ClientMessage message) { | ||||||
|  |         LOGGER.log(Level.INFO, "sending {0}", message); //NON-NLS | ||||||
|  |         if (client == null) | ||||||
|  |             app.errorDialog(lookup("lost.connection.to.server")); | ||||||
|  |         else | ||||||
|  |             client.send(message); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,122 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.input.controls.ActionListener; | ||||||
|  | import com.jme3.scene.Node; | ||||||
|  | import com.jme3.system.AppSettings; | ||||||
|  | import pp.battleship.client.BattleshipAppState; | ||||||
|  | import pp.battleship.model.IntPoint; | ||||||
|  |  | ||||||
|  | import java.lang.System.Logger; | ||||||
|  | import java.lang.System.Logger.Level; | ||||||
|  |  | ||||||
|  | import static pp.battleship.client.BattleshipApp.CLICK; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Represents the state responsible for managing the battle interface within the Battleship game. | ||||||
|  |  * This state handles the display and interaction of the battle map, including the opponent's map. | ||||||
|  |  * It manages GUI components, input events, and the layout of the interface when this state is enabled. | ||||||
|  |  */ | ||||||
|  | public class BattleAppState extends BattleshipAppState { | ||||||
|  |     private static final Logger LOGGER = System.getLogger(BattleAppState.class.getName()); | ||||||
|  |     private static final float DEPTH = 0f; | ||||||
|  |     private static final float GAP = 20f; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * A listener for handling click events in the battle interface. | ||||||
|  |      * When a click is detected, it triggers the corresponding actions on the opponent's map. | ||||||
|  |      */ | ||||||
|  |     private final ActionListener clickListener = (name, isPressed, tpf) -> click(isPressed); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The root node for all GUI components in the battle state. | ||||||
|  |      */ | ||||||
|  |     private final Node battleNode = new Node("Battle"); //NON-NLS | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * A view representing the opponent's map in the GUI. | ||||||
|  |      */ | ||||||
|  |     private MapView opponentMapView; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Enables the battle state by initializing, laying out, and adding GUI components. | ||||||
|  |      * Attaches the components to the GUI node and registers input listeners. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void enableState() { | ||||||
|  |         battleNode.detachAllChildren(); | ||||||
|  |         initializeGuiComponents(); | ||||||
|  |         layoutGuiComponents(); | ||||||
|  |         addGuiComponents(); | ||||||
|  |         getApp().getGuiNode().attachChild(battleNode); | ||||||
|  |         getApp().getInputManager().addListener(clickListener, CLICK); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Disables the battle state by removing GUI components and unregistering input listeners. | ||||||
|  |      * Also handles cleanup of resources, such as the opponent's map view. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void disableState() { | ||||||
|  |         getApp().getGuiNode().detachChild(battleNode); | ||||||
|  |         getApp().getInputManager().removeListener(clickListener); | ||||||
|  |         if (opponentMapView != null) { | ||||||
|  |             opponentMapView.unregister(); | ||||||
|  |             opponentMapView = null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initializes the GUI components used in the battle state. | ||||||
|  |      * Creates the opponent's map view and adds a grid overlay to it. | ||||||
|  |      */ | ||||||
|  |     private void initializeGuiComponents() { | ||||||
|  |         opponentMapView = new MapView(getGameLogic().getOpponentMap(), getApp()); | ||||||
|  |         opponentMapView.addGrid(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Adds the initialized GUI components to the battle node. | ||||||
|  |      * Currently, it attaches the opponent's map view to the node. | ||||||
|  |      */ | ||||||
|  |     private void addGuiComponents() { | ||||||
|  |         battleNode.attachChild(opponentMapView.getNode()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Lays out the GUI components within the window, positioning them appropriately. | ||||||
|  |      * The opponent's map view is positioned based on the window's dimensions and a specified gap. | ||||||
|  |      */ | ||||||
|  |     private void layoutGuiComponents() { | ||||||
|  |         final AppSettings s = getApp().getContext().getSettings(); | ||||||
|  |         final float mapWidth = opponentMapView.getWidth(); | ||||||
|  |         final float mapHeight = opponentMapView.getHeight(); | ||||||
|  |         final float windowWidth = s.getWidth(); | ||||||
|  |         final float windowHeight = s.getHeight(); | ||||||
|  |  | ||||||
|  |         opponentMapView.getNode().setLocalTranslation(windowWidth - mapWidth - GAP, | ||||||
|  |                                                       windowHeight - mapHeight - GAP, | ||||||
|  |                                                       DEPTH); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles click events in the battle interface. If the event indicates a click (not a release), | ||||||
|  |      * it translates the cursor position to the model's coordinate system and triggers the game logic | ||||||
|  |      * for interacting with the opponent's map. | ||||||
|  |      * | ||||||
|  |      * @param isPressed whether the mouse button is currently pressed (true) or released (false) | ||||||
|  |      */ | ||||||
|  |     private void click(boolean isPressed) { | ||||||
|  |         if (!isPressed || showsDialog()) | ||||||
|  |             return; | ||||||
|  |         final IntPoint cursorPos = opponentMapView.mouseToModel(getApp().getInputManager().getCursorPosition()); | ||||||
|  |         LOGGER.log(Level.DEBUG, "click: {0}", cursorPos); //NON-NLS | ||||||
|  |         getGameLogic().clickOpponentMap(cursorPos); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,173 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.input.controls.ActionListener; | ||||||
|  | import com.jme3.math.Vector2f; | ||||||
|  | import com.jme3.scene.Node; | ||||||
|  | import com.jme3.system.AppSettings; | ||||||
|  | import com.simsilica.lemur.Button; | ||||||
|  | import com.simsilica.lemur.Container; | ||||||
|  | import pp.battleship.client.BattleshipAppState; | ||||||
|  |  | ||||||
|  | import java.lang.System.Logger; | ||||||
|  | import java.lang.System.Logger.Level; | ||||||
|  |  | ||||||
|  | import static pp.battleship.Resources.lookup; | ||||||
|  | import static pp.battleship.client.BattleshipApp.CLICK; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * EditorState manages the editor mode in the Battleship game, | ||||||
|  |  * allowing players to place and rotate ships. | ||||||
|  |  */ | ||||||
|  | public class EditorAppState extends BattleshipAppState { | ||||||
|  |     private static final Logger LOGGER = System.getLogger(EditorAppState.class.getName()); | ||||||
|  |     private static final float DEPTH = 0f; | ||||||
|  |     private static final float GAP = 20f; | ||||||
|  |  | ||||||
|  |     private final ActionListener clickListener = (name, isPressed, tpf) -> click(isPressed); | ||||||
|  |     private final Node editorNode = new Node("Editor"); //NON-NLS | ||||||
|  |     private Container buttonContainer; | ||||||
|  |     private Button rotateButton; | ||||||
|  |     private Button readyButton; | ||||||
|  |     private MapView ownMapView; | ||||||
|  |     private MapView harborView; | ||||||
|  |     private Vector2f oldCursorPosition; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Enables the editor state by attaching necessary nodes and listeners. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void enableState() { | ||||||
|  |         editorNode.detachAllChildren(); | ||||||
|  |         initializeGuiComponents(); | ||||||
|  |         addGuiComponents(); | ||||||
|  |         layoutGuiComponents(); | ||||||
|  |         getApp().getGuiNode().attachChild(editorNode); | ||||||
|  |         getApp().getInputManager().addListener(clickListener, CLICK); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Disables the editor state by detaching nodes and removing listeners. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void disableState() { | ||||||
|  |         getApp().getGuiNode().detachChild(editorNode); | ||||||
|  |         getApp().getInputManager().removeListener(clickListener); | ||||||
|  |         if (ownMapView != null) { | ||||||
|  |             ownMapView.unregister(); | ||||||
|  |             ownMapView = null; | ||||||
|  |         } | ||||||
|  |         if (harborView != null) { | ||||||
|  |             harborView.unregister(); | ||||||
|  |             harborView = null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Updates the editor state, handling cursor movement and enabling buttons. | ||||||
|  |      * | ||||||
|  |      * @param tpf Time per frame | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void update(float tpf) { | ||||||
|  |         super.update(tpf); | ||||||
|  |         cursorMovement(); | ||||||
|  |         enableButtons(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Enables or disables buttons based on the logic state. | ||||||
|  |      */ | ||||||
|  |     private void enableButtons() { | ||||||
|  |         readyButton.setEnabled(getGameLogic().isMapComplete()); | ||||||
|  |         rotateButton.setEnabled(getGameLogic().movingShip()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles cursor movement for previewing ship placement. | ||||||
|  |      */ | ||||||
|  |     private void cursorMovement() { | ||||||
|  |         if (!getGameLogic().movingShip() || ownMapView == null || showsDialog()) | ||||||
|  |             return; | ||||||
|  |         final Vector2f cursorPosition = getApp().getInputManager().getCursorPosition(); | ||||||
|  |         if (!cursorPosition.equals(oldCursorPosition)) { | ||||||
|  |             oldCursorPosition = new Vector2f(cursorPosition); | ||||||
|  |             getGameLogic().movePreview(ownMapView.mouseToModel(cursorPosition)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initializes the GUI components for the editor. | ||||||
|  |      */ | ||||||
|  |     private void initializeGuiComponents() { | ||||||
|  |         ownMapView = new MapView(getGameLogic().getOwnMap(), getApp()); | ||||||
|  |         harborView = new MapView(getGameLogic().getHarbor(), getApp()); | ||||||
|  |         ownMapView.addGrid(); | ||||||
|  |         rotateButton = new Button(lookup("button.rotate")); | ||||||
|  |         readyButton = new Button(lookup("button.ready")); | ||||||
|  |         rotateButton.addClickCommands(e -> { | ||||||
|  |             if (!showsDialog()) | ||||||
|  |                 getGameLogic().rotateShip(); | ||||||
|  |         }); | ||||||
|  |         readyButton.addClickCommands(e -> { | ||||||
|  |             if (!showsDialog()) | ||||||
|  |                 getGameLogic().mapFinished(); | ||||||
|  |         }); | ||||||
|  |         buttonContainer = new Container(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Adds the GUI components to the editor node. | ||||||
|  |      */ | ||||||
|  |     private void addGuiComponents() { | ||||||
|  |         buttonContainer.addChild(rotateButton, 0, 0); | ||||||
|  |         buttonContainer.addChild(readyButton, 0, 1); | ||||||
|  |         editorNode.attachChild(ownMapView.getNode()); | ||||||
|  |         editorNode.attachChild(harborView.getNode()); | ||||||
|  |         editorNode.attachChild(buttonContainer); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Lays out the GUI components on the screen. | ||||||
|  |      */ | ||||||
|  |     private void layoutGuiComponents() { | ||||||
|  |         final AppSettings s = getApp().getContext().getSettings(); | ||||||
|  |         final float harborWidth = harborView.getWidth(); | ||||||
|  |         final float harborHeight = harborView.getHeight(); | ||||||
|  |         final float ownMapWidth = ownMapView.getWidth(); | ||||||
|  |         final float ownMapHeight = ownMapView.getHeight(); | ||||||
|  |         final float windowWidth = s.getWidth(); | ||||||
|  |         final float windowHeight = s.getHeight(); | ||||||
|  |  | ||||||
|  |         ownMapView.getNode() | ||||||
|  |                   .setLocalTranslation(0.5f * (windowWidth - harborWidth - ownMapWidth - GAP), | ||||||
|  |                                        0.5f * (windowHeight - ownMapHeight), | ||||||
|  |                                        DEPTH); | ||||||
|  |  | ||||||
|  |         harborView.getNode() | ||||||
|  |                   .setLocalTranslation(0.5f * (windowWidth - harborWidth + ownMapWidth + GAP), | ||||||
|  |                                        0.5f * (windowHeight - harborHeight), | ||||||
|  |                                        DEPTH); | ||||||
|  |  | ||||||
|  |         buttonContainer.setLocalTranslation(0.5f * (windowWidth - harborWidth - ownMapWidth - GAP), | ||||||
|  |                                             0.5f * (windowHeight - ownMapHeight - GAP), | ||||||
|  |                                             DEPTH); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles click events to place or rotate ships on the maps. | ||||||
|  |      */ | ||||||
|  |     private void click(boolean isPressed) { | ||||||
|  |         if (!isPressed || showsDialog()) return; | ||||||
|  |         final Vector2f cursorPos = getApp().getInputManager().getCursorPosition(); | ||||||
|  |         LOGGER.log(Level.DEBUG, "click: {0}", cursorPos); //NON-NLS | ||||||
|  |         getGameLogic().clickHarbor(harborView.mouseToModel(cursorPos)); | ||||||
|  |         getGameLogic().clickOwnMap(ownMapView.mouseToModel(cursorPos)); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,122 @@ | |||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import static pp.util.PreferencesUtils.getPreferences; | ||||||
|  |  | ||||||
|  | import java.lang.System.Logger; | ||||||
|  | import java.lang.System.Logger.Level; | ||||||
|  | import java.util.prefs.Preferences; | ||||||
|  |  | ||||||
|  | import com.jme3.app.Application; | ||||||
|  | import com.jme3.app.state.AbstractAppState; | ||||||
|  | import com.jme3.app.state.AppStateManager; | ||||||
|  | import com.jme3.asset.AssetLoadException; | ||||||
|  | import com.jme3.asset.AssetNotFoundException; | ||||||
|  | import com.jme3.audio.AudioData; | ||||||
|  | import com.jme3.audio.AudioNode; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Handles the background music beeing played. Is able to start and stop the music. Set the Volume of the Audio. | ||||||
|  |  */ | ||||||
|  | public class GameMusic extends AbstractAppState{ | ||||||
|  |     private static final Logger LOGGER = System.getLogger(GameMusic.class.getName()); | ||||||
|  |     private static final Preferences PREFERENCES = getPreferences(GameMusic.class); | ||||||
|  |     private static final String ENABLED_PREF = "enabled"; //NON-NLS | ||||||
|  |     private static final String VOLUME_PREF = "volume"; //NON-NLS | ||||||
|  |  | ||||||
|  |     private AudioNode music; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Checks if sound is enabled in the preferences. | ||||||
|  |      * | ||||||
|  |      * @return {@code true} if sound is enabled, {@code false} otherwise. | ||||||
|  |      */ | ||||||
|  |     public static boolean enabledInPreferences() { | ||||||
|  |         return PREFERENCES.getBoolean(ENABLED_PREF, true); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |      * Checks if sound is enabled in the preferences. | ||||||
|  |      * | ||||||
|  |      * @return float to which the volume is set | ||||||
|  |      */ | ||||||
|  |     public static float volumeInPreferences() { | ||||||
|  |         return PREFERENCES.getFloat(VOLUME_PREF, 0.5f); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initializes the sound effects for the game. | ||||||
|  |      * Overrides {@link AbstractAppState#initialize(AppStateManager, Application)} | ||||||
|  |      * | ||||||
|  |      * @param stateManager The state manager | ||||||
|  |      * @param app          The application | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void initialize(AppStateManager stateManager, Application app) { | ||||||
|  |         super.initialize(stateManager, app); | ||||||
|  |         music = loadSound(app, "Sound/background.ogg"); | ||||||
|  |         setVolume(volumeInPreferences()); | ||||||
|  |         music.setLooping(true); | ||||||
|  |         if (isEnabled() && music != null) { | ||||||
|  |             music.play(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Loads a sound from the specified file. | ||||||
|  |      * | ||||||
|  |      * @param app  The application | ||||||
|  |      * @param name The name of the sound file. | ||||||
|  |      * @return The loaded AudioNode. | ||||||
|  |      */ | ||||||
|  |     private AudioNode loadSound(Application app, String name) { | ||||||
|  |         try { | ||||||
|  |             final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer); | ||||||
|  |             sound.setLooping(false); | ||||||
|  |             sound.setPositional(false); | ||||||
|  |             return sound; | ||||||
|  |         } | ||||||
|  |         catch (AssetLoadException | AssetNotFoundException ex) { | ||||||
|  |             LOGGER.log(Level.ERROR, ex.getMessage(), ex); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sets the enabled state of this AppState. | ||||||
|  |      * Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)} | ||||||
|  |      * | ||||||
|  |      * @param enabled {@code true} to enable the AppState, {@code false} to disable it. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void setEnabled(boolean enabled) { | ||||||
|  |         if (isEnabled() == enabled) return; | ||||||
|  |  | ||||||
|  |         if (music != null) { | ||||||
|  |             if (enabled) { | ||||||
|  |                 music.play(); | ||||||
|  |             } else { | ||||||
|  |                 music.stop(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |      | ||||||
|  |         super.setEnabled(enabled); | ||||||
|  |         LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS | ||||||
|  |         PREFERENCES.putBoolean(ENABLED_PREF, enabled); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Toggles the game sound on or off. | ||||||
|  |      */ | ||||||
|  |     public void toggleSound() { | ||||||
|  |         setEnabled(!isEnabled()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sets the volume of music | ||||||
|  |      * @param vol the volume to which the music should be set | ||||||
|  |      */ | ||||||
|  |     public void setVolume(float vol){ | ||||||
|  |         music.setVolume(vol); | ||||||
|  |         PREFERENCES.putFloat(VOLUME_PREF, vol); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,191 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.material.Material; | ||||||
|  | import com.jme3.material.RenderState.BlendMode; | ||||||
|  | import com.jme3.math.ColorRGBA; | ||||||
|  | import com.jme3.math.Vector2f; | ||||||
|  | import com.jme3.math.Vector3f; | ||||||
|  | import com.jme3.scene.Geometry; | ||||||
|  | import com.jme3.scene.Node; | ||||||
|  | import com.jme3.scene.Spatial.CullHint; | ||||||
|  | import com.jme3.scene.shape.Quad; | ||||||
|  | import pp.battleship.client.BattleshipApp; | ||||||
|  | import pp.battleship.model.IntPoint; | ||||||
|  | import pp.battleship.model.ShipMap; | ||||||
|  | import pp.util.FloatPoint; | ||||||
|  | import pp.util.Position; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Represents the visual view of a {@link ShipMap}, used to display the map structure such as the player's map, harbor, | ||||||
|  |  * and opponent's map. This class handles the graphical representation of the map, including background setup, grid lines, | ||||||
|  |  * and interaction between the model and the view. | ||||||
|  |  */ | ||||||
|  | class MapView { | ||||||
|  |     public static final float FIELD_SIZE = 40f; | ||||||
|  |     private static final float GRID_LINE_WIDTH = 2f; | ||||||
|  |     private static final float BACKGROUND_DEPTH = -4f; | ||||||
|  |     private static final float GRID_DEPTH = -1f; | ||||||
|  |     private static final ColorRGBA BACKGROUND_COLOR = new ColorRGBA(0, 0.05f, 0.05f, 0.5f); | ||||||
|  |     private static final ColorRGBA GRID_COLOR = ColorRGBA.Green; | ||||||
|  |  | ||||||
|  |     // Reference to the main application and the ship map being visualized | ||||||
|  |     private final BattleshipApp app; | ||||||
|  |     private final Node mapNode = new Node("map"); // NON-NLS | ||||||
|  |     private final ShipMap map; | ||||||
|  |     private final MapViewSynchronizer synchronizer; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a new MapView for a given {@link ShipMap} and {@link BattleshipApp}. | ||||||
|  |      * Initializes the view by setting up the background and registering a synchronizer to listen to changes in the map. | ||||||
|  |      * | ||||||
|  |      * @param map the ship map to visualize | ||||||
|  |      * @param app the main application instance | ||||||
|  |      */ | ||||||
|  |     MapView(ShipMap map, BattleshipApp app) { | ||||||
|  |         this.map = map; | ||||||
|  |         this.app = app; | ||||||
|  |         this.synchronizer = new MapViewSynchronizer(this); | ||||||
|  |         setupBackground(); | ||||||
|  |         app.getGameLogic().addListener(synchronizer); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Unregisters the {@link MapViewSynchronizer} from the listener list of the ClientGameLogic, | ||||||
|  |      * stopping the view from receiving updates when the underlying {@link ShipMap} changes. | ||||||
|  |      * After calling this method, this MapView instance should no longer be used. | ||||||
|  |      */ | ||||||
|  |     void unregister() { | ||||||
|  |         app.getGameLogic().removeListener(synchronizer); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the {@link ShipMap} associated with this view. | ||||||
|  |      * | ||||||
|  |      * @return the ship map | ||||||
|  |      */ | ||||||
|  |     public ShipMap getMap() { | ||||||
|  |         return map; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the {@link BattleshipApp} instance associated with this view. | ||||||
|  |      * | ||||||
|  |      * @return the main application instance | ||||||
|  |      */ | ||||||
|  |     public BattleshipApp getApp() { | ||||||
|  |         return app; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sets up the background of the map view using a quad geometry. | ||||||
|  |      * The background is configured with a semi-transparent color and placed at a specific depth. | ||||||
|  |      */ | ||||||
|  |     private void setupBackground() { | ||||||
|  |         final Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); // NON-NLS | ||||||
|  |         mat.setColor("Color", BACKGROUND_COLOR); // NON-NLS | ||||||
|  |         mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); | ||||||
|  |         final Position corner = modelToView(map.getWidth(), map.getHeight()); | ||||||
|  |         final Geometry background = new Geometry("MapBackground", new Quad(corner.getX(), corner.getY())); | ||||||
|  |         background.setMaterial(mat); | ||||||
|  |         background.setLocalTranslation(0f, 0f, BACKGROUND_DEPTH); | ||||||
|  |         background.setCullHint(CullHint.Never); | ||||||
|  |         mapNode.attachChild(background); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Adds grid lines to the map view to visually separate the fields within the map. | ||||||
|  |      * The grid lines are drawn based on the dimensions of the ship map. | ||||||
|  |      */ | ||||||
|  |     public void addGrid() { | ||||||
|  |         for (int x = 0; x <= map.getWidth(); x++) { | ||||||
|  |             final Position f = modelToView(x, 0); | ||||||
|  |             final Position t = modelToView(x, map.getHeight()); | ||||||
|  |             mapNode.attachChild(gridLine(f, t)); | ||||||
|  |         } | ||||||
|  |         for (int y = 0; y <= map.getHeight(); y++) { | ||||||
|  |             final Position f = modelToView(0, y); | ||||||
|  |             final Position t = modelToView(map.getWidth(), y); | ||||||
|  |             mapNode.attachChild(gridLine(f, t)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the root node containing all visual elements in this map view. | ||||||
|  |      * | ||||||
|  |      * @return the root node for the map view | ||||||
|  |      */ | ||||||
|  |     public Node getNode() { | ||||||
|  |         return mapNode; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the total width of the map in view coordinates. | ||||||
|  |      * | ||||||
|  |      * @return the width of the map in view coordinates | ||||||
|  |      */ | ||||||
|  |     public float getWidth() { | ||||||
|  |         return FIELD_SIZE * map.getWidth(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the total height of the map in view coordinates. | ||||||
|  |      * | ||||||
|  |      * @return the height of the map in view coordinates | ||||||
|  |      */ | ||||||
|  |     public float getHeight() { | ||||||
|  |         return FIELD_SIZE * map.getHeight(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Converts coordinates from view coordinates to model coordinates. | ||||||
|  |      * | ||||||
|  |      * @param x the x-coordinate in view space | ||||||
|  |      * @param y the y-coordinate in view space | ||||||
|  |      * @return the corresponding model coordinates as an {@link IntPoint} | ||||||
|  |      */ | ||||||
|  |     public IntPoint viewToModel(float x, float y) { | ||||||
|  |         return new IntPoint((int) Math.floor(x / FIELD_SIZE), (int) Math.floor(y / FIELD_SIZE)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Converts coordinates from model coordinates to view coordinates. | ||||||
|  |      * | ||||||
|  |      * @param x the x-coordinate in model space | ||||||
|  |      * @param y the y-coordinate in model space | ||||||
|  |      * @return the corresponding view coordinates as a {@link Position} | ||||||
|  |      */ | ||||||
|  |     public Position modelToView(float x, float y) { | ||||||
|  |         return new FloatPoint(x * FIELD_SIZE, y * FIELD_SIZE); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Converts the mouse position to model coordinates. | ||||||
|  |      * This method takes into account the map's transformation in the 3D scene. | ||||||
|  |      * | ||||||
|  |      * @param pos the 2D vector representing the mouse position in the view | ||||||
|  |      * @return the corresponding model coordinates as an {@link IntPoint} | ||||||
|  |      */ | ||||||
|  |     public IntPoint mouseToModel(Vector2f pos) { | ||||||
|  |         final Vector3f world = new Vector3f(pos.getX(), pos.getY(), 0f); | ||||||
|  |         final Vector3f view = mapNode.getWorldTransform().transformInverseVector(world, null); | ||||||
|  |         return viewToModel(view.getX(), view.getY()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a visual representation of a grid line between two positions. | ||||||
|  |      * | ||||||
|  |      * @param p1 the start position of the grid line | ||||||
|  |      * @param p2 the end position of the grid line | ||||||
|  |      * @return a {@link Geometry} representing the grid line | ||||||
|  |      */ | ||||||
|  |     private Geometry gridLine(Position p1, Position p2) { | ||||||
|  |         return app.getDraw().makeFatLine(p1, p2, GRID_DEPTH, GRID_COLOR, GRID_LINE_WIDTH); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,154 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.material.Material; | ||||||
|  | import com.jme3.material.RenderState; | ||||||
|  | import com.jme3.material.RenderState.BlendMode; | ||||||
|  | import com.jme3.math.ColorRGBA; | ||||||
|  | import com.jme3.scene.Geometry; | ||||||
|  | import com.jme3.scene.Node; | ||||||
|  | import com.jme3.scene.Spatial; | ||||||
|  | import com.jme3.scene.shape.Sphere; | ||||||
|  |  | ||||||
|  | import pp.battleship.model.Battleship; | ||||||
|  | import pp.battleship.model.Shell; | ||||||
|  | import pp.battleship.model.Shot; | ||||||
|  | import pp.util.Position; | ||||||
|  |  | ||||||
|  | import static com.jme3.material.Materials.UNSHADED; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Synchronizes the visual representation of the ship map with the game model. | ||||||
|  |  * It handles the rendering of ships and shots on the map view, updating the view | ||||||
|  |  * whenever changes occur in the model. | ||||||
|  |  */ | ||||||
|  | class MapViewSynchronizer extends ShipMapSynchronizer { | ||||||
|  |     // Constants for rendering properties | ||||||
|  |     private static final float SHIP_LINE_WIDTH = 6f; | ||||||
|  |     private static final float SHOT_DEPTH = -2f; | ||||||
|  |     private static final float SHIP_DEPTH = 0f; | ||||||
|  |     private static final float INDENT = 4f; | ||||||
|  |  | ||||||
|  |     // Colors used for different visual elements | ||||||
|  |     private static final ColorRGBA HIT_COLOR = ColorRGBA.Red; | ||||||
|  |     private static final ColorRGBA MISS_COLOR = ColorRGBA.Blue; | ||||||
|  |     private static final ColorRGBA SHIP_BORDER_COLOR = ColorRGBA.White; | ||||||
|  |     private static final ColorRGBA PREVIEW_COLOR = ColorRGBA.Gray; | ||||||
|  |     private static final ColorRGBA ERROR_COLOR = ColorRGBA.Red; | ||||||
|  |  | ||||||
|  |     // The MapView associated with this synchronizer | ||||||
|  |     private final MapView view; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a new MapViewSynchronizer for the given MapView. | ||||||
|  |      * Initializes the synchronizer and adds existing elements from the model to the view. | ||||||
|  |      * | ||||||
|  |      * @param view the MapView to synchronize with the game model | ||||||
|  |      */ | ||||||
|  |     public MapViewSynchronizer(MapView view) { | ||||||
|  |         super(view.getMap(), view.getNode()); | ||||||
|  |         this.view = view; | ||||||
|  |         addExisting(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a visual representation of a shot on the map. | ||||||
|  |      * A hit shot is represented in red, while a miss is represented in blue. | ||||||
|  |      * | ||||||
|  |      * @param shot the Shot object representing the shot in the model | ||||||
|  |      * @return a Spatial representing the shot on the map | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public Spatial visit(Shot shot) { | ||||||
|  |         // Convert the shot's model coordinates to view coordinates | ||||||
|  |         final Position p1 = view.modelToView(shot.getX(), shot.getY()); | ||||||
|  |         final Position p2 = view.modelToView(shot.getX() + 1, shot.getY() + 1); | ||||||
|  |         final ColorRGBA color = shot.isHit() ? HIT_COLOR : MISS_COLOR; | ||||||
|  |  | ||||||
|  |         // Create and return a rectangle representing the shot | ||||||
|  |         return view.getApp().getDraw().makeRectangle(p1.getX(), p1.getY(), | ||||||
|  |                                                      SHOT_DEPTH, | ||||||
|  |                                                      p2.getX() - p1.getX(), p2.getY() - p1.getY(), | ||||||
|  |                                                      color); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a visual representation of a battleship on the map. | ||||||
|  |      * The ship's border color depends on its status: normal, valid preview, or invalid preview. | ||||||
|  |      * | ||||||
|  |      * @param ship the Battleship object representing the ship in the model | ||||||
|  |      * @return a Spatial representing the ship on the map | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public Spatial visit(Battleship ship) { | ||||||
|  |         // Create a node to represent the ship | ||||||
|  |         final Node shipNode = new Node("ship"); //NON-NLS | ||||||
|  |  | ||||||
|  |         // Convert the ship's model coordinates to view coordinates | ||||||
|  |         final Position p1 = view.modelToView(ship.getMinX(), ship.getMinY()); | ||||||
|  |         final Position p2 = view.modelToView(ship.getMaxX() + 1, ship.getMaxY() + 1); | ||||||
|  |  | ||||||
|  |         // Calculate the coordinates for the ship's bounding box | ||||||
|  |         final float x1 = p1.getX() + INDENT; | ||||||
|  |         final float y1 = p1.getY() + INDENT; | ||||||
|  |         final float x2 = p2.getX() - INDENT; | ||||||
|  |         final float y2 = p2.getY() - INDENT; | ||||||
|  |  | ||||||
|  |         // Determine the color based on the ship's status | ||||||
|  |         final ColorRGBA color = switch (ship.getStatus()) { | ||||||
|  |             case NORMAL -> SHIP_BORDER_COLOR; | ||||||
|  |             case VALID_PREVIEW -> PREVIEW_COLOR; | ||||||
|  |             case INVALID_PREVIEW -> ERROR_COLOR; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // Add the ship's borders to the node | ||||||
|  |         shipNode.attachChild(shipLine(x1, y1, x2, y1, color)); | ||||||
|  |         shipNode.attachChild(shipLine(x1, y2, x2, y2, color)); | ||||||
|  |         shipNode.attachChild(shipLine(x1, y1, x1, y2, color)); | ||||||
|  |         shipNode.attachChild(shipLine(x2, y1, x2, y2, color)); | ||||||
|  |  | ||||||
|  |         // Return the complete ship representation | ||||||
|  |         return shipNode; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a line geometry representing part of the ship's border. | ||||||
|  |      * | ||||||
|  |      * @param x1    the starting x-coordinate of the line | ||||||
|  |      * @param y1    the starting y-coordinate of the line | ||||||
|  |      * @param x2    the ending x-coordinate of the line | ||||||
|  |      * @param y2    the ending y-coordinate of the line | ||||||
|  |      * @param color the color of the line | ||||||
|  |      * @return a Geometry representing the line | ||||||
|  |      */ | ||||||
|  |     private Geometry shipLine(float x1, float y1, float x2, float y2, ColorRGBA color) { | ||||||
|  |         return view.getApp().getDraw().makeFatLine(x1, y1, x2, y2, SHIP_DEPTH, color, SHIP_LINE_WIDTH); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |      * Creates and returns a Spatial representation of the given {@code Shell} object | ||||||
|  |      * for 2D visualization in the game. The shell is represented as a circle. | ||||||
|  |      * | ||||||
|  |      * @param shell The {@code Shell} object to be visualized. | ||||||
|  |      * @return A {@code Spatial} object representing the shell on the map. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public Spatial visit(Shell shell) { | ||||||
|  |         final ColorRGBA color = ColorRGBA.Black; | ||||||
|  |         Geometry ellipse = new Geometry("ellipse", new Sphere(50, 50, MapView.FIELD_SIZE / 2 * 0.8f)); | ||||||
|  |         Material mat = new Material(view.getApp().getAssetManager(), UNSHADED); //NON-NLS | ||||||
|  |         mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); | ||||||
|  |         mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); | ||||||
|  |         mat.setColor("Color", color); | ||||||
|  |         ellipse.setMaterial(mat); | ||||||
|  |         ellipse.addControl(new Shell2DControl(view, shell)); | ||||||
|  |         return ellipse; | ||||||
|  |     }       | ||||||
|  | } | ||||||
| @@ -0,0 +1,285 @@ | |||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.effect.ParticleEmitter; | ||||||
|  | import com.jme3.effect.ParticleMesh.Type; | ||||||
|  | import com.jme3.effect.shapes.EmitterSphereShape; | ||||||
|  | import com.jme3.material.Material; | ||||||
|  | import com.jme3.math.ColorRGBA; | ||||||
|  | import com.jme3.math.FastMath; | ||||||
|  | import com.jme3.math.Vector3f; | ||||||
|  | import pp.battleship.client.BattleshipApp; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Factory class responsible for creating particle effects used in the game. | ||||||
|  |  * This centralizes the creation of various types of particle emitters. | ||||||
|  |  */ | ||||||
|  | public class ParticleEffectFactory { | ||||||
|  |     private static final int COUNT_FACTOR = 1; | ||||||
|  |     private static final float COUNT_FACTOR_F = 1f; | ||||||
|  |     private static final boolean POINT_SPRITE = true; | ||||||
|  |     private static final Type EMITTER_TYPE = POINT_SPRITE ? Type.Point : Type.Triangle; | ||||||
|  |      | ||||||
|  |     private final BattleshipApp app; | ||||||
|  |  | ||||||
|  |     ParticleEffectFactory(BattleshipApp app) { | ||||||
|  |         this.app = app; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a flame particle emitter. | ||||||
|  |      *  | ||||||
|  |      * @return a configured flame particle emitter | ||||||
|  |      */ | ||||||
|  |     ParticleEmitter createFlame() { | ||||||
|  |         ParticleEmitter flame = new ParticleEmitter("Flame", EMITTER_TYPE, 32 * COUNT_FACTOR); | ||||||
|  |         flame.setSelectRandomImage(true); | ||||||
|  |         flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F))); | ||||||
|  |         flame.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); | ||||||
|  |         flame.setStartSize(0.1f); | ||||||
|  |         flame.setEndSize(0.5f); | ||||||
|  |         flame.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); | ||||||
|  |         flame.setParticlesPerSec(0); | ||||||
|  |         flame.setGravity(0, -5, 0); | ||||||
|  |         flame.setLowLife(.4f); | ||||||
|  |         flame.setHighLife(.5f); | ||||||
|  |         flame.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 7, 0)); | ||||||
|  |         flame.getParticleInfluencer().setVelocityVariation(1f); | ||||||
|  |         flame.setImagesX(2); | ||||||
|  |         flame.setImagesY(2); | ||||||
|  |         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||||
|  |         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png")); | ||||||
|  |         mat.setBoolean("PointSprite", POINT_SPRITE); | ||||||
|  |         flame.setMaterial(mat); | ||||||
|  |  | ||||||
|  |         return flame; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a flash particle emitter. | ||||||
|  |      *  | ||||||
|  |      * @return a configured flash particle emitter | ||||||
|  |      */ | ||||||
|  |     ParticleEmitter createFlash() { | ||||||
|  |         ParticleEmitter flash = new ParticleEmitter("Flash", EMITTER_TYPE, 24 * COUNT_FACTOR); | ||||||
|  |         flash.setSelectRandomImage(true); | ||||||
|  |         flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1f / COUNT_FACTOR_F)); | ||||||
|  |         flash.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); | ||||||
|  |         flash.setStartSize(.1f); | ||||||
|  |         flash.setEndSize(0.5f); | ||||||
|  |         flash.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f)); | ||||||
|  |         flash.setParticlesPerSec(0); | ||||||
|  |         flash.setGravity(0, 0, 0); | ||||||
|  |         flash.setLowLife(.2f); | ||||||
|  |         flash.setHighLife(.2f); | ||||||
|  |         flash.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 5f, 0)); | ||||||
|  |         flash.getParticleInfluencer().setVelocityVariation(1); | ||||||
|  |         flash.setImagesX(2); | ||||||
|  |         flash.setImagesY(2); | ||||||
|  |         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||||
|  |         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flash.png")); | ||||||
|  |         mat.setBoolean("PointSprite", POINT_SPRITE); | ||||||
|  |         flash.setMaterial(mat); | ||||||
|  |  | ||||||
|  |         return flash; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a round spark particle emitter. | ||||||
|  |      *  | ||||||
|  |      * @return a configured round spark particle emitter | ||||||
|  |      */ | ||||||
|  |     ParticleEmitter createRoundSpark() { | ||||||
|  |         ParticleEmitter roundSpark = new ParticleEmitter("RoundSpark", EMITTER_TYPE, 20 * COUNT_FACTOR); | ||||||
|  |         roundSpark.setStartColor(new ColorRGBA(1f, 0.29f, 0.34f, (float) (1.0 / COUNT_FACTOR_F))); | ||||||
|  |         roundSpark.setEndColor(new ColorRGBA(0, 0, 0, 0.5f / COUNT_FACTOR_F)); | ||||||
|  |         roundSpark.setStartSize(0.2f); | ||||||
|  |         roundSpark.setEndSize(0.8f); | ||||||
|  |         roundSpark.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); | ||||||
|  |         roundSpark.setParticlesPerSec(0); | ||||||
|  |         roundSpark.setGravity(0, -.5f, 0); | ||||||
|  |         roundSpark.setLowLife(1.8f); | ||||||
|  |         roundSpark.setHighLife(2f); | ||||||
|  |         roundSpark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 3, 0)); | ||||||
|  |         roundSpark.getParticleInfluencer().setVelocityVariation(.5f); | ||||||
|  |         roundSpark.setImagesX(1); | ||||||
|  |         roundSpark.setImagesY(1); | ||||||
|  |         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||||
|  |         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/roundspark.png")); | ||||||
|  |         mat.setBoolean("PointSprite", POINT_SPRITE); | ||||||
|  |         roundSpark.setMaterial(mat); | ||||||
|  |  | ||||||
|  |         return roundSpark; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a spark particle emitter. | ||||||
|  |      *  | ||||||
|  |      * @return a configured spark particle emitter | ||||||
|  |      */ | ||||||
|  |     ParticleEmitter createSpark() { | ||||||
|  |         ParticleEmitter spark = new ParticleEmitter("Spark", Type.Triangle, 30 * COUNT_FACTOR); | ||||||
|  |         spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F)); | ||||||
|  |         spark.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); | ||||||
|  |         spark.setStartSize(.5f); | ||||||
|  |         spark.setEndSize(.5f); | ||||||
|  |         spark.setFacingVelocity(true); | ||||||
|  |         spark.setParticlesPerSec(0); | ||||||
|  |         spark.setGravity(0, 5, 0); | ||||||
|  |         spark.setLowLife(1.1f); | ||||||
|  |         spark.setHighLife(1.5f); | ||||||
|  |         spark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 20, 0)); | ||||||
|  |         spark.getParticleInfluencer().setVelocityVariation(1); | ||||||
|  |         spark.setImagesX(1); | ||||||
|  |         spark.setImagesY(1); | ||||||
|  |         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||||
|  |         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/spark.png")); | ||||||
|  |         spark.setMaterial(mat); | ||||||
|  |  | ||||||
|  |         return spark; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a smoke trail particle emitter. | ||||||
|  |      *  | ||||||
|  |      * @return a configured smoke trail particle emitter | ||||||
|  |      */ | ||||||
|  |     ParticleEmitter createSmokeTrail() { | ||||||
|  |         ParticleEmitter smokeTrail = new ParticleEmitter("SmokeTrail", Type.Triangle, 22 * COUNT_FACTOR); | ||||||
|  |         smokeTrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F)); | ||||||
|  |         smokeTrail.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); | ||||||
|  |         smokeTrail.setStartSize(.2f); | ||||||
|  |         smokeTrail.setEndSize(1f); | ||||||
|  |         smokeTrail.setFacingVelocity(true); | ||||||
|  |         smokeTrail.setParticlesPerSec(0); | ||||||
|  |         smokeTrail.setGravity(0, 1, 0); | ||||||
|  |         smokeTrail.setLowLife(.4f); | ||||||
|  |         smokeTrail.setHighLife(.5f); | ||||||
|  |         smokeTrail.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 12, 0)); | ||||||
|  |         smokeTrail.getParticleInfluencer().setVelocityVariation(1); | ||||||
|  |         smokeTrail.setImagesX(1); | ||||||
|  |         smokeTrail.setImagesY(3); | ||||||
|  |         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||||
|  |         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/smoketrail.png")); | ||||||
|  |         smokeTrail.setMaterial(mat); | ||||||
|  |  | ||||||
|  |         return smokeTrail; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a debris particle emitter. | ||||||
|  |      *  | ||||||
|  |      * @return a configured debris particle emitter | ||||||
|  |      */ | ||||||
|  |     ParticleEmitter createDebris() { | ||||||
|  |         ParticleEmitter debris = new ParticleEmitter("Debris", Type.Triangle, 15 * COUNT_FACTOR); | ||||||
|  |         debris.setSelectRandomImage(true); | ||||||
|  |         debris.setRandomAngle(true); | ||||||
|  |         debris.setRotateSpeed(FastMath.TWO_PI * 4); | ||||||
|  |         debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, 1.0f / COUNT_FACTOR_F)); | ||||||
|  |         debris.setEndColor(new ColorRGBA(.5f, 0.5f, 0.5f, 0f)); | ||||||
|  |         debris.setStartSize(.10f); | ||||||
|  |         debris.setEndSize(.15f); | ||||||
|  |         debris.setParticlesPerSec(0); | ||||||
|  |         debris.setGravity(0, 12f, 0); | ||||||
|  |         debris.setLowLife(1.4f); | ||||||
|  |         debris.setHighLife(1.5f); | ||||||
|  |         debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 15, 0)); | ||||||
|  |         debris.getParticleInfluencer().setVelocityVariation(.60f); | ||||||
|  |         debris.setImagesX(3); | ||||||
|  |         debris.setImagesY(3); | ||||||
|  |         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||||
|  |         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/Debris.png")); | ||||||
|  |         debris.setMaterial(mat); | ||||||
|  |  | ||||||
|  |         return debris; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a shockwave particle emitter. | ||||||
|  |      *  | ||||||
|  |      * @return a configured shockwave particle emitter | ||||||
|  |      */ | ||||||
|  |     ParticleEmitter createShockwave() { | ||||||
|  |         ParticleEmitter shockwave = new ParticleEmitter("Shockwave", Type.Triangle, 1 * COUNT_FACTOR); | ||||||
|  |         shockwave.setFaceNormal(Vector3f.UNIT_Y); | ||||||
|  |         shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, .8f / COUNT_FACTOR_F)); | ||||||
|  |         shockwave.setEndColor(new ColorRGBA(.48f, 0.17f, 0.01f, 0f)); | ||||||
|  |         shockwave.setStartSize(0f); | ||||||
|  |         shockwave.setEndSize(3f); | ||||||
|  |         shockwave.setParticlesPerSec(0); | ||||||
|  |         shockwave.setGravity(0, 0, 0); | ||||||
|  |         shockwave.setLowLife(0.5f); | ||||||
|  |         shockwave.setHighLife(0.5f); | ||||||
|  |         shockwave.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0)); | ||||||
|  |         shockwave.getParticleInfluencer().setVelocityVariation(0f); | ||||||
|  |         shockwave.setImagesX(1); | ||||||
|  |         shockwave.setImagesY(1); | ||||||
|  |         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||||
|  |         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/shockwave.png")); | ||||||
|  |         shockwave.setMaterial(mat); | ||||||
|  |  | ||||||
|  |         return shockwave; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a moving smoke emitter. | ||||||
|  |      *  | ||||||
|  |      * @return a configured smoke emitter | ||||||
|  |      */ | ||||||
|  |     ParticleEmitter createMovingSmokeEmitter() { | ||||||
|  |         ParticleEmitter smokeEmitter = new ParticleEmitter("SmokeEmitter", Type.Triangle, 300); | ||||||
|  |         smokeEmitter.setGravity(0, 0, 0); | ||||||
|  |         smokeEmitter.getParticleInfluencer().setVelocityVariation(1); | ||||||
|  |         smokeEmitter.setLowLife(1); | ||||||
|  |         smokeEmitter.setHighLife(1); | ||||||
|  |         smokeEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, .5f, 0)); | ||||||
|  |         smokeEmitter.setImagesX(15); // Assuming the smoke texture is a sprite sheet with 15 frames | ||||||
|  |         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||||
|  |         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Smoke/Smoke.png")); | ||||||
|  |         smokeEmitter.setMaterial(mat); | ||||||
|  |  | ||||||
|  |         return smokeEmitter; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a one-time water splash particle emitter. | ||||||
|  |      *  | ||||||
|  |      * @return a configured one-time water splash particle emitter | ||||||
|  |      */ | ||||||
|  |     public ParticleEmitter createWaterSplash() { | ||||||
|  |         // Create a new particle emitter for the splash effect | ||||||
|  |         ParticleEmitter waterSplash = new ParticleEmitter("WaterSplash", Type.Triangle, 30); | ||||||
|  |          | ||||||
|  |         // Set the shape of the emitter, making particles emit from a point or small area | ||||||
|  |         waterSplash.setShape(new EmitterSphereShape(Vector3f.ZERO, 0.2f)); | ||||||
|  |          | ||||||
|  |         // Start and end colors for water (blue, fading out) | ||||||
|  |         waterSplash.setStartColor(new ColorRGBA(0.4f, 0.4f, 1f, 1f));  // Light blue at start | ||||||
|  |         waterSplash.setEndColor(new ColorRGBA(0.4f, 0.4f, 1f, 0f));    // Transparent at the end | ||||||
|  |          | ||||||
|  |         // Particle size: small at start, larger before fading out | ||||||
|  |         waterSplash.setStartSize(0.1f); | ||||||
|  |         waterSplash.setEndSize(0.3f); | ||||||
|  |  | ||||||
|  |         // Particle lifespan (how long particles live) | ||||||
|  |         waterSplash.setLowLife(0.5f); | ||||||
|  |         waterSplash.setHighLife(1f); | ||||||
|  |  | ||||||
|  |         // Gravity: Pull the water particles downwards | ||||||
|  |         waterSplash.setGravity(0, -9.81f, 0);  // Earth's gravity simulation | ||||||
|  |          | ||||||
|  |         // Velocity: Give particles an initial burst upward (simulates splash) | ||||||
|  |         waterSplash.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 3, 0)); | ||||||
|  |         waterSplash.getParticleInfluencer().setVelocityVariation(0.6f);  // Add randomness to splash | ||||||
|  |          | ||||||
|  |         // Set how many particles are emitted per second (0 to emit all particles at once) | ||||||
|  |         waterSplash.setParticlesPerSec(0); | ||||||
|  |          | ||||||
|  |         // Load a texture for the water splash (assuming a texture exists at this path) | ||||||
|  |         Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); | ||||||
|  |         mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Splash/splash.png")); | ||||||
|  |         waterSplash.setMaterial(mat); | ||||||
|  |  | ||||||
|  |         return waterSplash; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,203 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.app.Application; | ||||||
|  | import com.jme3.app.state.AppStateManager; | ||||||
|  | import com.jme3.asset.AssetManager; | ||||||
|  | import com.jme3.light.AmbientLight; | ||||||
|  | import com.jme3.light.DirectionalLight; | ||||||
|  | import com.jme3.material.Material; | ||||||
|  | import com.jme3.math.ColorRGBA; | ||||||
|  | import com.jme3.math.Vector2f; | ||||||
|  | import com.jme3.math.Vector3f; | ||||||
|  | import com.jme3.renderer.Camera; | ||||||
|  | import com.jme3.renderer.queue.RenderQueue.ShadowMode; | ||||||
|  | import com.jme3.scene.Geometry; | ||||||
|  | import com.jme3.scene.Node; | ||||||
|  | import com.jme3.scene.Spatial; | ||||||
|  | import com.jme3.scene.shape.Box; | ||||||
|  | import com.jme3.shadow.DirectionalLightShadowRenderer; | ||||||
|  | import com.jme3.shadow.EdgeFilteringMode; | ||||||
|  | import com.jme3.texture.Texture; | ||||||
|  | import com.jme3.util.SkyFactory; | ||||||
|  | import com.jme3.util.TangentBinormalGenerator; | ||||||
|  | import pp.battleship.client.BattleshipAppState; | ||||||
|  | import pp.battleship.model.ShipMap; | ||||||
|  | import pp.util.FloatMath; | ||||||
|  |  | ||||||
|  | import static pp.util.FloatMath.TWO_PI; | ||||||
|  | import static pp.util.FloatMath.cos; | ||||||
|  | import static pp.util.FloatMath.sin; | ||||||
|  | import static pp.util.FloatMath.sqrt; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Manages the rendering and visual aspects of the sea and sky in the Battleship game. | ||||||
|  |  * This state is responsible for setting up and updating the sea, sky, and lighting | ||||||
|  |  * conditions, and controls the camera to create a dynamic view of the game environment. | ||||||
|  |  */ | ||||||
|  | public class SeaAppState extends BattleshipAppState { | ||||||
|  |     /** | ||||||
|  |      * The path to the sea texture material. | ||||||
|  |      */ | ||||||
|  |     private static final String SEA_TEXTURE = "Textures/Terrain/Water/Water.j3m"; //NON-NLS | ||||||
|  |     private static final float ABOVE_SEA_LEVEL = 4f; | ||||||
|  |     private static final float INCLINATION = 2.5f; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The root node for all visual elements in this state. | ||||||
|  |      */ | ||||||
|  |     private final Node viewNode = new Node("view"); //NON-NLS | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The node containing the scene elements, such as the sea surface. | ||||||
|  |      */ | ||||||
|  |     private final Node sceneNode = new Node("scene"); //NON-NLS | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Synchronizes the sea's visual representation with the game logic. | ||||||
|  |      */ | ||||||
|  |     private SeaSynchronizer synchronizer; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The current angle of the camera around the center of the map. | ||||||
|  |      */ | ||||||
|  |     private float cameraAngle; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Initializes the state by setting up the sky, lights, and other visual components. | ||||||
|  |      * This method is called when the state is first attached to the state manager. | ||||||
|  |      * | ||||||
|  |      * @param stateManager the state manager | ||||||
|  |      * @param application  the application | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void initialize(AppStateManager stateManager, Application application) { | ||||||
|  |         super.initialize(stateManager, application); | ||||||
|  |         viewNode.attachChild(sceneNode); | ||||||
|  |         setupLights(); | ||||||
|  |         setupSky(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Enables the sea and sky state, setting up the scene and registering any necessary listeners. | ||||||
|  |      * This method is called when the state is set to active. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void enableState() { | ||||||
|  |         sceneNode.detachAllChildren(); | ||||||
|  |         setupScene(); | ||||||
|  |         if (synchronizer == null) { | ||||||
|  |             synchronizer = new SeaSynchronizer(getApp(), sceneNode, getGameLogic().getOwnMap()); | ||||||
|  |             getGameLogic().addListener(synchronizer); | ||||||
|  |         } | ||||||
|  |         getApp().getRootNode().attachChild(viewNode); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Disables the sea and sky state, removing visual elements from the scene and unregistering listeners. | ||||||
|  |      * This method is called when the state is set to inactive. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void disableState() { | ||||||
|  |         getApp().getRootNode().detachChild(viewNode); | ||||||
|  |         if (synchronizer != null) { | ||||||
|  |             getGameLogic().removeListener(synchronizer); | ||||||
|  |             synchronizer = null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Updates the state each frame, moving the camera to simulate it circling around the map. | ||||||
|  |      * | ||||||
|  |      * @param tpf the time per frame (seconds) | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void update(float tpf) { | ||||||
|  |         super.update(tpf); | ||||||
|  |         cameraAngle += TWO_PI * 0.05f * tpf; | ||||||
|  |         adjustCamera(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sets up the lighting for the scene, including directional and ambient lights. | ||||||
|  |      * Also configures shadows to enhance the visual depth of the scene. | ||||||
|  |      */ | ||||||
|  |     private void setupLights() { | ||||||
|  |         final AssetManager assetManager = getApp().getAssetManager(); | ||||||
|  |         final DirectionalLightShadowRenderer shRend = new DirectionalLightShadowRenderer(assetManager, 2048, 3); | ||||||
|  |         shRend.setLambda(0.55f); | ||||||
|  |         shRend.setShadowIntensity(0.6f); | ||||||
|  |         shRend.setEdgeFilteringMode(EdgeFilteringMode.Bilinear); | ||||||
|  |         getApp().getViewPort().addProcessor(shRend); | ||||||
|  |  | ||||||
|  |         final DirectionalLight sun = new DirectionalLight(); | ||||||
|  |         sun.setDirection(new Vector3f(-1f, -0.7f, -1f).normalizeLocal()); | ||||||
|  |         viewNode.addLight(sun); | ||||||
|  |         shRend.setLight(sun); | ||||||
|  |  | ||||||
|  |         final AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.3f, 0.3f, 0.3f, 0f)); | ||||||
|  |         viewNode.addLight(ambientLight); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sets up the sky in the scene using a skybox with textures for all six directions. | ||||||
|  |      * This creates a realistic and immersive environment for the sea. | ||||||
|  |      */ | ||||||
|  |     private void setupSky() { | ||||||
|  |         final AssetManager assetManager = getApp().getAssetManager(); | ||||||
|  |         final Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg"); //NON-NLS | ||||||
|  |         final Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg"); //NON-NLS | ||||||
|  |         final Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg"); //NON-NLS | ||||||
|  |         final Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg"); //NON-NLS | ||||||
|  |         final Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg"); //NON-NLS | ||||||
|  |         final Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg"); //NON-NLS | ||||||
|  |         final Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down); | ||||||
|  |         sky.rotate(0f, FloatMath.PI, 0f); | ||||||
|  |         viewNode.attachChild(sky); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sets up the sea surface in the scene. This includes creating the sea mesh, | ||||||
|  |      * applying textures, and enabling shadows. | ||||||
|  |      */ | ||||||
|  |     private void setupScene() { | ||||||
|  |         final ShipMap ownMap = getGameLogic().getOwnMap(); | ||||||
|  |         final float x = 0.5f * ownMap.getWidth(); | ||||||
|  |         final float y = 0.5f * ownMap.getHeight(); | ||||||
|  |         final Box seaMesh = new Box(y + 0.5f, 0.1f, x + 0.5f); | ||||||
|  |         final Geometry seaGeo = new Geometry("sea", seaMesh); //NON-NLS | ||||||
|  |         seaGeo.setLocalTranslation(new Vector3f(y, -0.1f, x)); | ||||||
|  |         seaMesh.scaleTextureCoordinates(new Vector2f(4f, 4f)); | ||||||
|  |         final Material seaMat = getApp().getAssetManager().loadMaterial(SEA_TEXTURE); | ||||||
|  |         seaGeo.setMaterial(seaMat); | ||||||
|  |         seaGeo.setShadowMode(ShadowMode.CastAndReceive); | ||||||
|  |         TangentBinormalGenerator.generate(seaGeo); | ||||||
|  |         sceneNode.attachChild(seaGeo); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Adjusts the camera position and orientation to create a circular motion around | ||||||
|  |      * the center of the map. This provides a dynamic view of the sea and surrounding environment. | ||||||
|  |      */ | ||||||
|  |     private void adjustCamera() { | ||||||
|  |         final ShipMap ownMap = getGameLogic().getOwnMap(); | ||||||
|  |         final float mx = 0.5f * ownMap.getWidth(); | ||||||
|  |         final float my = 0.5f * ownMap.getHeight(); | ||||||
|  |         final float radius = 2f * sqrt(mx * mx + my + my); | ||||||
|  |         final float cos = radius * cos(cameraAngle); | ||||||
|  |         final float sin = radius * sin(cameraAngle); | ||||||
|  |         final float x = mx - cos; | ||||||
|  |         final float y = my - sin; | ||||||
|  |         final Camera camera = getApp().getCamera(); | ||||||
|  |         camera.setLocation(new Vector3f(y, ABOVE_SEA_LEVEL, x)); | ||||||
|  |         camera.getRotation().lookAt(new Vector3f(sin, -INCLINATION, cos), | ||||||
|  |                                     Vector3f.UNIT_Y); | ||||||
|  |         camera.update(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,378 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.effect.ParticleEmitter; | ||||||
|  | import com.jme3.material.Material; | ||||||
|  | import com.jme3.material.RenderState.BlendMode; | ||||||
|  | import com.jme3.math.ColorRGBA; | ||||||
|  | import com.jme3.renderer.queue.RenderQueue.ShadowMode; | ||||||
|  | import com.jme3.scene.Geometry; | ||||||
|  | import com.jme3.scene.Node; | ||||||
|  | import com.jme3.scene.Spatial; | ||||||
|  | import com.jme3.scene.shape.Box; | ||||||
|  | import com.jme3.scene.shape.Cylinder; | ||||||
|  | import pp.battleship.client.BattleshipApp; | ||||||
|  | import pp.battleship.model.Battleship; | ||||||
|  | import pp.battleship.model.Rotation; | ||||||
|  | import pp.battleship.model.Shell; | ||||||
|  | import pp.battleship.model.ShipMap; | ||||||
|  | import pp.battleship.model.Shot; | ||||||
|  |  | ||||||
|  | import static java.util.Objects.requireNonNull; | ||||||
|  | import static pp.util.FloatMath.HALF_PI; | ||||||
|  | import static pp.util.FloatMath.PI; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The {@code SeaSynchronizer} class is responsible for synchronizing the graphical | ||||||
|  |  * representation of the ships and shots on the sea map with the underlying data model. | ||||||
|  |  * It extends the {@link ShipMapSynchronizer} to provide specific synchronization | ||||||
|  |  * logic for the sea map. | ||||||
|  |  */ | ||||||
|  | class SeaSynchronizer extends ShipMapSynchronizer { | ||||||
|  |     private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS | ||||||
|  |     private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; //NON-NLS | ||||||
|  |     private static final String BOAT_SMALL_MODEL = "Models/BoatSmall/12219_boat_v2_L2.j3o"; //NON-NLS | ||||||
|  |     private static final String CV_MODEL = "Models/CV/CV.j3o"; //NON-NLS | ||||||
|  |     private static final String BATTLE_MODEL = "Models/Battle/Battle.j3o"; //NON-NLS | ||||||
|  |     private static final String LIGHTING = "Common/MatDefs/Light/Lighting.j3md"; | ||||||
|  |     private static final String COLOR = "Color"; //NON-NLS | ||||||
|  |     private static final String SHIP = "ship"; //NON-NLS | ||||||
|  |     private static final String SHOT = "shot"; //NON-NLS | ||||||
|  |     private static final ColorRGBA BOX_COLOR = ColorRGBA.Gray; | ||||||
|  |     private static final ColorRGBA SPLASH_COLOR = new ColorRGBA(0f, 0f, 1f, 0.4f); | ||||||
|  |     private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f); | ||||||
|  |  | ||||||
|  |     private final ShipMap map; | ||||||
|  |     private final BattleshipApp app; | ||||||
|  |     private final ParticleEffectFactory particleFactory; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map. | ||||||
|  |      * | ||||||
|  |      * @param app  the Battleship application | ||||||
|  |      * @param root the root node to which graphical elements will be attached | ||||||
|  |      * @param map  the ship map containing the ships and shots | ||||||
|  |      */ | ||||||
|  |     public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) { | ||||||
|  |         super(app.getGameLogic().getOwnMap(), root); | ||||||
|  |         this.app = app; | ||||||
|  |         this.map = map; | ||||||
|  |         this.particleFactory = new ParticleEffectFactory(app); | ||||||
|  |         addExisting(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Visits a {@link Shot} and creates a graphical representation of it. | ||||||
|  |      * If the shot is a hit, it attaches the representation to the ship node. | ||||||
|  |      * | ||||||
|  |      * @param shot the shot to be represented | ||||||
|  |      * @return the graphical representation of the shot, or null if the shot is a hit | ||||||
|  |      * and the representation has been attached to the ship node | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public Spatial visit(Shot shot) { | ||||||
|  |         return shot.isHit() ? handleHit(shot) : handleMiss(shot); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles a miss by representing it with a blue cylinder | ||||||
|  |      * and attaching a water splash effect to it. | ||||||
|  |      * @param shot the shot to be processed | ||||||
|  |      * @return a Spatial simulating a miss with water splash effect | ||||||
|  |      */ | ||||||
|  |     private Spatial handleMiss(Shot shot) { | ||||||
|  |         Node shotNode = new Node("ShotNode"); | ||||||
|  |         Geometry shotCylinder = createCylinder(shot); | ||||||
|  |         shotNode.attachChild(shotCylinder); | ||||||
|  |         ParticleEmitter waterSplash = particleFactory.createWaterSplash(); | ||||||
|  |         waterSplash.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f); | ||||||
|  |         shotNode.attachChild(waterSplash); | ||||||
|  |         waterSplash.emitAllParticles(); | ||||||
|  |  | ||||||
|  |         return shotNode; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles the sinking animation and removal of ship if destroyed | ||||||
|  |      * @param ship the ship to be sunk | ||||||
|  |      */ | ||||||
|  |     private void sinkAndRemoveShip(Battleship ship) { | ||||||
|  |         Battleship wilkeningklaunichtmeinencode = ship; | ||||||
|  |         final Node shipNode = (Node) getSpatial(wilkeningklaunichtmeinencode); | ||||||
|  |         if (shipNode == null) return; | ||||||
|  |  | ||||||
|  |         // Add sinking control to animate the sinking | ||||||
|  |         shipNode.addControl(new SinkingControl(shipNode)); | ||||||
|  |  | ||||||
|  |         // Add particle effects | ||||||
|  |         ParticleEmitter bubbles = particleFactory.createWaterSplash(); | ||||||
|  |         bubbles.setLocalTranslation(shipNode.getLocalTranslation()); | ||||||
|  |         shipNode.attachChild(bubbles); | ||||||
|  |         bubbles.emitAllParticles(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles a hit by attaching its representation to the node that | ||||||
|  |      * contains the ship model as a child so that it moves with the ship. | ||||||
|  |      * | ||||||
|  |      * @param shot a hit | ||||||
|  |      * @return always null to prevent the representation from being attached to the items node as well | ||||||
|  |      */ | ||||||
|  |     private Spatial handleHit(Shot shot) { | ||||||
|  |         final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship"); | ||||||
|  |         final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node"); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         // Create a new node specifically for the hit effects | ||||||
|  |         Node hitEffectNode = new Node("HitEffectNode"); | ||||||
|  |  | ||||||
|  |         // Create particle effects | ||||||
|  |         ParticleEmitter flame = particleFactory.createFlame(); | ||||||
|  |         ParticleEmitter flash = particleFactory.createFlash(); | ||||||
|  |         ParticleEmitter spark = particleFactory.createSpark(); | ||||||
|  |         ParticleEmitter roundSpark = particleFactory.createRoundSpark(); | ||||||
|  |         ParticleEmitter smokeTrail = particleFactory.createSmokeTrail(); | ||||||
|  |         ParticleEmitter debris = particleFactory.createDebris(); | ||||||
|  |         ParticleEmitter shockwave = particleFactory.createShockwave(); | ||||||
|  |         ParticleEmitter movingSmoke = particleFactory.createMovingSmokeEmitter(); | ||||||
|  |  | ||||||
|  |         // Attach all effects to the hitEffectNode | ||||||
|  |         hitEffectNode.attachChild(flame); | ||||||
|  |         hitEffectNode.attachChild(flash); | ||||||
|  |         hitEffectNode.attachChild(spark); | ||||||
|  |         hitEffectNode.attachChild(roundSpark); | ||||||
|  |         hitEffectNode.attachChild(smokeTrail); | ||||||
|  |         hitEffectNode.attachChild(debris); | ||||||
|  |         hitEffectNode.attachChild(shockwave); | ||||||
|  |         hitEffectNode.attachChild(movingSmoke); | ||||||
|  |  | ||||||
|  |         // Set the local translation for the hit effect to the point of impact | ||||||
|  |         hitEffectNode.setLocalTranslation(shot.getY() + 0.5f - shipNode.getLocalTranslation().x, | ||||||
|  |                                           0.5f, // Adjust as needed for height above the ship | ||||||
|  |                                           shot.getX() + 0.5f - shipNode.getLocalTranslation().z); | ||||||
|  |  | ||||||
|  |         // Attach the hitEffectNode to the shipNode so it moves with the ship | ||||||
|  |         shipNode.attachChild(hitEffectNode); | ||||||
|  |  | ||||||
|  |         // Emit particles when the hit happens | ||||||
|  |         flash.emitAllParticles(); | ||||||
|  |         spark.emitAllParticles(); | ||||||
|  |         smokeTrail.emitAllParticles(); | ||||||
|  |         debris.emitAllParticles(); | ||||||
|  |         shockwave.emitAllParticles(); | ||||||
|  |         flame.emitAllParticles(); | ||||||
|  |         roundSpark.emitAllParticles(); | ||||||
|  |  | ||||||
|  |         //Checks if ship is destroyed and triggers animation accordingly | ||||||
|  |         if (ship.isDestroyed()) { | ||||||
|  |             sinkAndRemoveShip(ship); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a cylinder geometry representing the specified shot. | ||||||
|  |      * The appearance of the cylinder depends on whether the shot is a hit or a miss. | ||||||
|  |      * | ||||||
|  |      * @param shot the shot to be represented | ||||||
|  |      * @return the geometry representing the shot | ||||||
|  |      */ | ||||||
|  |     private Geometry createCylinder(Shot shot) { | ||||||
|  |         final ColorRGBA color = shot.isHit() ? HIT_COLOR : SPLASH_COLOR; | ||||||
|  |         final float height = shot.isHit() ? 1.2f : 0.1f; | ||||||
|  |  | ||||||
|  |         final Cylinder cylinder = new Cylinder(2, 20, 0.45f, height, true); | ||||||
|  |         final Geometry geometry = new Geometry(SHOT, cylinder); | ||||||
|  |  | ||||||
|  |         geometry.setMaterial(createColoredMaterial(color)); | ||||||
|  |         geometry.rotate(HALF_PI, 0f, 0f); | ||||||
|  |         // compute the center of the shot in world coordinates | ||||||
|  |         geometry.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f); | ||||||
|  |  | ||||||
|  |         return geometry; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Visits a {@link Battleship} and creates a graphical representation of it. | ||||||
|  |      * The representation is either a 3D model or a simple box depending on the | ||||||
|  |      * type of battleship. | ||||||
|  |      * | ||||||
|  |      * @param ship the battleship to be represented | ||||||
|  |      * @return the node containing the graphical representation of the battleship | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public Spatial visit(Battleship ship) { | ||||||
|  |         final Node node = new Node(SHIP); | ||||||
|  |         node.attachChild(createShip(ship)); | ||||||
|  |         // compute the center of the ship in world coordinates | ||||||
|  |         final float x = 0.5f * (ship.getMinY() + ship.getMaxY() + 1f); | ||||||
|  |         final float z = 0.5f * (ship.getMinX() + ship.getMaxX() + 1f); | ||||||
|  |         node.setLocalTranslation(x, 0f, z); | ||||||
|  |         node.addControl(new ShipControl(ship)); | ||||||
|  |         return node; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates the appropriate graphical representation of the specified battleship. | ||||||
|  |      * The representation is either a detailed model or a simple box based on the length of the ship. | ||||||
|  |      * | ||||||
|  |      * @param ship the battleship to be represented | ||||||
|  |      * @return the spatial representing the battleship | ||||||
|  |      */ | ||||||
|  |     private Spatial createShip(Battleship ship) { | ||||||
|  |         switch (ship.getLength()) { | ||||||
|  |             case 4: return createBattleship(ship); | ||||||
|  |             case 3: return createCV(ship); | ||||||
|  |             case 2: return createBattle(ship); | ||||||
|  |             case 1: return createSmallship(ship); | ||||||
|  |             default: return createBox(ship); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a simple box to represent a battleship that is not of the "King George V" type. | ||||||
|  |      * | ||||||
|  |      * @param ship the battleship to be represented | ||||||
|  |      * @return the geometry representing the battleship as a box | ||||||
|  |      */ | ||||||
|  |     private Spatial createBox(Battleship ship) { | ||||||
|  |         final Box box = new Box(0.5f * (ship.getMaxY() - ship.getMinY()) + 0.3f, | ||||||
|  |                                 0.3f, | ||||||
|  |                                 0.5f * (ship.getMaxX() - ship.getMinX()) + 0.3f); | ||||||
|  |         final Geometry geometry = new Geometry(SHIP, box); | ||||||
|  |         geometry.setMaterial(createColoredMaterial(BOX_COLOR)); | ||||||
|  |         geometry.setShadowMode(ShadowMode.CastAndReceive); | ||||||
|  |  | ||||||
|  |         return geometry; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a new {@link Material} with the specified color. | ||||||
|  |      * If the color includes transparency (i.e., alpha value less than 1), | ||||||
|  |      * the material's render state is set to use alpha blending, allowing for | ||||||
|  |      * semi-transparent rendering. | ||||||
|  |      * | ||||||
|  |      * @param color the {@link ColorRGBA} to be applied to the material. If the alpha value | ||||||
|  |      *              of the color is less than 1, the material will support transparency. | ||||||
|  |      * @return a {@link Material} instance configured with the specified color and, | ||||||
|  |      * if necessary, alpha blending enabled. | ||||||
|  |      */ | ||||||
|  |     private Material createColoredMaterial(ColorRGBA color) { | ||||||
|  |         final Material material = new Material(app.getAssetManager(), UNSHADED); | ||||||
|  |         if (color.getAlpha() < 1f) | ||||||
|  |             material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); | ||||||
|  |         material.setColor(COLOR, color); | ||||||
|  |         return material; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a detailed 3D model to represent a "King George V" battleship. | ||||||
|  |      * | ||||||
|  |      * @param ship the battleship to be represented | ||||||
|  |      * @return the spatial representing the "King George V" battleship | ||||||
|  |      */ | ||||||
|  |     private Spatial createBattleship(Battleship ship) { | ||||||
|  |         final Spatial model = app.getAssetManager().loadModel(KING_GEORGE_V_MODEL); | ||||||
|  |  | ||||||
|  |         model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); | ||||||
|  |         model.scale(1.48f); | ||||||
|  |         // model.scale(0.0007f); | ||||||
|  |         model.setShadowMode(ShadowMode.CastAndReceive); | ||||||
|  |  | ||||||
|  |         return model; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     /** | ||||||
|  |      * Creates a detailed 3D model to represent a small tug boat. | ||||||
|  |      * | ||||||
|  |      * @param ship the battleship to be represented | ||||||
|  |      * @return the spatial representing a small tug boat | ||||||
|  |      */ | ||||||
|  |     private Spatial createSmallship(Battleship ship) { | ||||||
|  |         final Spatial model = app.getAssetManager().loadModel(BOAT_SMALL_MODEL); | ||||||
|  |  | ||||||
|  |         model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); | ||||||
|  |         model.scale(0.0005f); | ||||||
|  |         model.setShadowMode(ShadowMode.CastAndReceive); | ||||||
|  |  | ||||||
|  |         return model; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a detailed 3D model to represent a "German WWII UBoat". | ||||||
|  |      * | ||||||
|  |      * @param ship the battleship to be represented | ||||||
|  |      * @return the spatial representing the "German WWII UBoat" | ||||||
|  |      */ | ||||||
|  |     private Spatial createCV(Battleship ship) { | ||||||
|  |         final Spatial model = app.getAssetManager().loadModel(CV_MODEL); | ||||||
|  |  | ||||||
|  |         model.rotate(0f, calculateRotationAngle(ship.getRot()), 0f); | ||||||
|  |         model.move(0f, 0.25f, 0f); | ||||||
|  |         model.scale(0.85f); | ||||||
|  |         model.setShadowMode(ShadowMode.CastAndReceive); | ||||||
|  |  | ||||||
|  |         return model; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a detailed 3D model to represent a battleship. | ||||||
|  |      * | ||||||
|  |      * @param ship the battleship to be represented | ||||||
|  |      * @return the spatial representing a battleship | ||||||
|  |      */ | ||||||
|  |     private Spatial createBattle(Battleship ship) { | ||||||
|  |         final Spatial model = app.getAssetManager().loadModel(BATTLE_MODEL); | ||||||
|  |  | ||||||
|  |         model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); | ||||||
|  |         model.move(0f, -0.06f, 0f); | ||||||
|  |         model.scale(0.27f); | ||||||
|  |         model.setShadowMode(ShadowMode.CastAndReceive); | ||||||
|  |  | ||||||
|  |         return model; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Calculates the rotation angle for the specified rotation. | ||||||
|  |      * | ||||||
|  |      * @param rot the rotation of the battleship | ||||||
|  |      * @return the rotation angle in radians | ||||||
|  |      */ | ||||||
|  |     private static float calculateRotationAngle(Rotation rot) { | ||||||
|  |         return switch (rot) { | ||||||
|  |             case RIGHT -> HALF_PI; | ||||||
|  |             case DOWN -> 0f; | ||||||
|  |             case LEFT -> -HALF_PI; | ||||||
|  |             case UP -> PI; | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates and returns a 3D model representation of the given {@code Shell} object | ||||||
|  |      * for visualization in the game. | ||||||
|  |      * | ||||||
|  |      * @param shell The {@code Shell} object to be visualized. | ||||||
|  |      * @return A {@code Spatial} object representing the 3D model of the shell. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public Spatial visit(Shell shell) { | ||||||
|  |         final Spatial model = app.getAssetManager().loadModel("Models/Shell/shell.j3o"); | ||||||
|  |         model.setLocalScale(.05f); | ||||||
|  |         model.setShadowMode(ShadowMode.CastAndReceive); | ||||||
|  |         Material mat = new Material(app.getAssetManager(), LIGHTING); | ||||||
|  |         mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture("Models/Shell/shell_color.png")); | ||||||
|  |         mat.setReceivesShadows(true); | ||||||
|  |         model.setMaterial(mat); | ||||||
|  |  | ||||||
|  |         model.addControl(new ShellControl(shell)); | ||||||
|  |         return model; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,57 @@ | |||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.math.Vector3f; | ||||||
|  | import com.jme3.renderer.RenderManager; | ||||||
|  | import com.jme3.renderer.ViewPort; | ||||||
|  | import com.jme3.scene.control.AbstractControl; | ||||||
|  | import pp.battleship.model.Shell; | ||||||
|  | import pp.util.Position; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Controls the 2D representation of a {@code Shell} in the game, updating its position | ||||||
|  |  * based on the shell's current state in the game model. The {@code Shell2DControl} class | ||||||
|  |  * is responsible for translating the shell's 3D position to a 2D view position within | ||||||
|  |  * the game's map view. | ||||||
|  |  */ | ||||||
|  | public class Shell2DControl extends AbstractControl { | ||||||
|  |     private final Shell shell; | ||||||
|  |     private final MapView view; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a new {@code Shell2DControl} to manage the 2D visualization of the given {@code Shell}. | ||||||
|  |      * | ||||||
|  |      * @param view  The {@code MapView} used to get information about the map to display. | ||||||
|  |      * @param shell The {@code Shell} being visualized. | ||||||
|  |      */ | ||||||
|  |     public Shell2DControl(MapView view, Shell shell){ | ||||||
|  |         this.shell = shell; | ||||||
|  |         this.view = view; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Updates the position of the shell's 2D representation based on the shell's current | ||||||
|  |      * 3D position in the game model. The position is mapped from model space to view space | ||||||
|  |      * coordinates and translated to the appropriate location within the {@code MapView}. | ||||||
|  |      * | ||||||
|  |      * @param tpf Time per frame, representing the time elapsed since the last frame. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void controlUpdate(float tpf) { | ||||||
|  |         Vector3f shellPos = shell.getPosition(); | ||||||
|  |         Position viewPos = view.modelToView(shellPos.x, shellPos.z); | ||||||
|  |         spatial.setLocalTranslation(viewPos.getX() + MapView.FIELD_SIZE / 2, viewPos.getY() + MapView.FIELD_SIZE / 2, 0); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This method is called during the rendering phase, but it does not perform any | ||||||
|  |      * operations in this implementation as the control only influences the spatial's | ||||||
|  |      * transformation, not its rendering process. | ||||||
|  |      * | ||||||
|  |      * @param rm the RenderManager rendering the controlled Spatial (not null) | ||||||
|  |      * @param vp the ViewPort being rendered (not null) | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void controlRender(RenderManager rm, ViewPort vp) { | ||||||
|  |         // No rendering-specific behavior required for this control | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,58 @@ | |||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.math.Vector3f; | ||||||
|  | import com.jme3.renderer.RenderManager; | ||||||
|  | import com.jme3.renderer.ViewPort; | ||||||
|  | import com.jme3.scene.control.AbstractControl; | ||||||
|  | import pp.battleship.model.Shell; | ||||||
|  |  | ||||||
|  | import static pp.util.FloatMath.PI; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Controls the 3D representation of a {@code Shell} in the game, updating its position | ||||||
|  |  * and rotation based on the shell's current state in the game model. The {@code ShellControl} | ||||||
|  |  * class ensures that the spatial associated with the shell is positioned and oriented correctly | ||||||
|  |  * within the world. | ||||||
|  |  */ | ||||||
|  | public class ShellControl extends AbstractControl { | ||||||
|  |     private final Shell shell; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a new {@code ShellControl} to manage the 3D visualization of the given {@code Shell}. | ||||||
|  |      * | ||||||
|  |      * @param shell The {@code Shell} being visualized and controlled. | ||||||
|  |      */ | ||||||
|  |     public ShellControl(Shell shell){ | ||||||
|  |         super(); | ||||||
|  |         this.shell = shell; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Updates the 3D position and rotation of the shell based on its current state. | ||||||
|  |      * Converts map coordinates to world coordinates and applies the shell's orientation. | ||||||
|  |      * | ||||||
|  |      * @param tpf Time per frame, representing the elapsed time since the last update. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void controlUpdate(float tpf) { | ||||||
|  |         Vector3f pos = shell.getPosition(); | ||||||
|  |         Vector3f fixed = new Vector3f(pos.z + 0.5f, pos.y, pos.x + 0.5f); | ||||||
|  |         fixed.setY(pos.y); | ||||||
|  |         spatial.setLocalTranslation(fixed); | ||||||
|  |         spatial.setLocalRotation(shell.getRotation()); | ||||||
|  |         spatial.rotate(PI/2,0,0); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This method is called during the rendering phase, but it does not perform any | ||||||
|  |      * operations in this implementation as the control only influences the spatial's | ||||||
|  |      * transformation, not its rendering process. | ||||||
|  |      * | ||||||
|  |      * @param rm the RenderManager rendering the controlled Spatial (not null) | ||||||
|  |      * @param vp the ViewPort being rendered (not null) | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void controlRender(RenderManager rm, ViewPort vp) { | ||||||
|  |         // No rendering-specific behavior required for this control | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,106 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.math.Quaternion; | ||||||
|  | import com.jme3.math.Vector3f; | ||||||
|  | import com.jme3.renderer.RenderManager; | ||||||
|  | import com.jme3.renderer.ViewPort; | ||||||
|  | import com.jme3.scene.control.AbstractControl; | ||||||
|  | import pp.battleship.model.Battleship; | ||||||
|  |  | ||||||
|  | import static pp.util.FloatMath.DEG_TO_RAD; | ||||||
|  | import static pp.util.FloatMath.TWO_PI; | ||||||
|  | import static pp.util.FloatMath.sin; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Controls the oscillating pitch motion of a battleship model in the game. | ||||||
|  |  * The ship oscillates to simulate a realistic movement on water, based on its orientation and length. | ||||||
|  |  */ | ||||||
|  | class ShipControl extends AbstractControl { | ||||||
|  |     /** | ||||||
|  |      * The axis of rotation for the ship's pitch (tilting forward and backward). | ||||||
|  |      */ | ||||||
|  |     private final Vector3f axis; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The duration of one complete oscillation cycle in seconds. | ||||||
|  |      */ | ||||||
|  |     private final float cycle; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The amplitude of the pitch oscillation in radians, determining how much the ship tilts. | ||||||
|  |      */ | ||||||
|  |     private final float amplitude; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * A quaternion representing the ship's current pitch rotation. | ||||||
|  |      */ | ||||||
|  |     private final Quaternion pitch = new Quaternion(); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The current time within the oscillation cycle, used to calculate the ship's pitch angle. | ||||||
|  |      */ | ||||||
|  |     private float time; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a new ShipControl instance for the specified Battleship. | ||||||
|  |      * The ship's orientation determines the axis of rotation, while its length influences | ||||||
|  |      * the cycle duration and amplitude of the oscillation. | ||||||
|  |      * | ||||||
|  |      * @param ship the Battleship object to control | ||||||
|  |      */ | ||||||
|  |     public ShipControl(Battleship ship) { | ||||||
|  |         // Determine the axis of rotation based on the ship's orientation | ||||||
|  |         axis = switch (ship.getRot()) { | ||||||
|  |             case LEFT, RIGHT -> Vector3f.UNIT_X; | ||||||
|  |             case UP, DOWN -> Vector3f.UNIT_Z; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // Set the cycle duration and amplitude based on the ship's length | ||||||
|  |         cycle = ship.getLength() * 2f; | ||||||
|  |         amplitude = 5f * DEG_TO_RAD / ship.getLength(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Updates the ship's pitch oscillation each frame. The ship's pitch is adjusted | ||||||
|  |      * to create a continuous tilting motion, simulating the effect of waves. | ||||||
|  |      * | ||||||
|  |      * @param tpf time per frame (in seconds), used to calculate the new pitch angle | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void controlUpdate(float tpf) { | ||||||
|  |         // If spatial is null, do nothing | ||||||
|  |         if (spatial == null) return; | ||||||
|  |  | ||||||
|  |         // Update the time within the oscillation cycle | ||||||
|  |         time = (time + tpf) % cycle; | ||||||
|  |  | ||||||
|  |         // Calculate the current angle of the oscillation | ||||||
|  |         final float angle = amplitude * sin(time * TWO_PI / cycle); | ||||||
|  |  | ||||||
|  |         // Update the pitch Quaternion with the new angle | ||||||
|  |         pitch.fromAngleAxis(angle, axis); | ||||||
|  |  | ||||||
|  |         // Apply the pitch rotation to the spatial | ||||||
|  |         spatial.setLocalRotation(pitch); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This method is called during the rendering phase, but it does not perform any | ||||||
|  |      * operations in this implementation as the control only influences the spatial's | ||||||
|  |      * transformation, not its rendering process. | ||||||
|  |      * | ||||||
|  |      * @param rm the RenderManager rendering the controlled Spatial (not null) | ||||||
|  |      * @param vp the ViewPort being rendered (not null) | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void controlRender(RenderManager rm, ViewPort vp) { | ||||||
|  |         // No rendering logic is needed for this control | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,89 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.scene.Node; | ||||||
|  | import com.jme3.scene.Spatial; | ||||||
|  | import pp.battleship.model.Item; | ||||||
|  | import pp.battleship.model.ShipMap; | ||||||
|  | import pp.battleship.model.Visitor; | ||||||
|  | import pp.battleship.notification.GameEventListener; | ||||||
|  | import pp.battleship.notification.ItemAddedEvent; | ||||||
|  | import pp.battleship.notification.ItemRemovedEvent; | ||||||
|  | import pp.view.ModelViewSynchronizer; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Abstract base class for synchronizing the visual representation of a {@link ShipMap} with its model state. | ||||||
|  |  * This class handles the addition and removal of items from the ship map, ensuring that changes in the model | ||||||
|  |  * are accurately reflected in the view. | ||||||
|  |  * <p> | ||||||
|  |  * Subclasses are responsible for providing the specific implementation of how each item in the map | ||||||
|  |  * is represented visually by implementing the {@link Visitor} interface. | ||||||
|  |  * </p> | ||||||
|  |  */ | ||||||
|  | abstract class ShipMapSynchronizer extends ModelViewSynchronizer<Item> implements Visitor<Spatial>, GameEventListener { | ||||||
|  |     // The ship map that this synchronizer is responsible for | ||||||
|  |     private final ShipMap shipMap; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a new ShipMapSynchronizer. | ||||||
|  |      * Initializes the synchronizer with the provided ship map and the root node for attaching view representations. | ||||||
|  |      * | ||||||
|  |      * @param map  the ship map to be synchronized | ||||||
|  |      * @param root the root node to which the view representations of the ship map items are attached | ||||||
|  |      */ | ||||||
|  |     protected ShipMapSynchronizer(ShipMap map, Node root) { | ||||||
|  |         super(root); | ||||||
|  |         this.shipMap = map; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Translates a model item into its corresponding visual representation. | ||||||
|  |      * The specific visual representation is determined by the concrete implementation of the {@link Visitor} interface. | ||||||
|  |      * | ||||||
|  |      * @param item the item from the model to be translated | ||||||
|  |      * @return the visual representation of the item as a {@link Spatial} | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected Spatial translate(Item item) { | ||||||
|  |         return item.accept(this); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Adds the existing items from the ship map to the view. | ||||||
|  |      * This method should be called during initialization to ensure that all current items in the ship map | ||||||
|  |      * are visually represented. | ||||||
|  |      */ | ||||||
|  |     protected void addExisting() { | ||||||
|  |         shipMap.getItems().forEach(this::add); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles the event when an item is removed from the ship map. | ||||||
|  |      * Removes the visual representation of the item from the view if it belongs to the synchronized ship map. | ||||||
|  |      * | ||||||
|  |      * @param event the event indicating that an item has been removed from the ship map | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void receivedEvent(ItemRemovedEvent event) { | ||||||
|  |         if (shipMap == event.map()) | ||||||
|  |             delete(event.item()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Handles the event when an item is added to the ship map. | ||||||
|  |      * Adds the visual representation of the new item to the view if it belongs to the synchronized ship map. | ||||||
|  |      * | ||||||
|  |      * @param event the event indicating that an item has been added to the ship map | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void receivedEvent(ItemAddedEvent event) { | ||||||
|  |         if (shipMap == event.map()) | ||||||
|  |             add(event.item()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,61 @@ | |||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.jme3.scene.control.AbstractControl; | ||||||
|  | import com.jme3.math.Vector3f; | ||||||
|  | import com.jme3.renderer.RenderManager; | ||||||
|  | import com.jme3.renderer.ViewPort; | ||||||
|  | import com.jme3.scene.Node; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Control that handles the sinking effect for destroyed ships. | ||||||
|  |  * It will gradually move the ship downwards and then remove it from the scene. | ||||||
|  |  */ | ||||||
|  | class SinkingControl extends AbstractControl { | ||||||
|  |     private static final float SINK_DURATION = 5f;  // Duration of the sinking animation | ||||||
|  |     private static final float SINK_SPEED = 0.1f;   // Speed at which the ship sinks | ||||||
|  |     private float elapsedTime = 0; | ||||||
|  |  | ||||||
|  |     private final Node shipNode; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs a {@code SinkingControl} object with the shipNode to be to be sunk | ||||||
|  |      * @param shipNode the node to handeld | ||||||
|  |      */ | ||||||
|  |     public SinkingControl(Node shipNode) { | ||||||
|  |         this.shipNode = shipNode; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Updated the Map to sink the ship | ||||||
|  |      *  | ||||||
|  |      * @param tpf time per frame | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void controlUpdate(float tpf) { | ||||||
|  |         // Update the sinking effect | ||||||
|  |         elapsedTime += tpf; | ||||||
|  |  | ||||||
|  |         // Move the ship down over time | ||||||
|  |         Vector3f currentPos = shipNode.getLocalTranslation(); | ||||||
|  |         shipNode.setLocalTranslation(currentPos.x, currentPos.y - SINK_SPEED * tpf, currentPos.z); | ||||||
|  |  | ||||||
|  |         // Check if sinking duration has passed | ||||||
|  |         if (elapsedTime >= SINK_DURATION) { | ||||||
|  |             // Remove the ship from the scene | ||||||
|  |             shipNode.removeFromParent(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This method is called during the rendering phase, but it does not perform any | ||||||
|  |      * operations in this implementation as the control only influences the spatial's | ||||||
|  |      * transformation, not its rendering process. | ||||||
|  |      * | ||||||
|  |      * @param rm the RenderManager rendering the controlled Spatial (not null) | ||||||
|  |      * @param vp the ViewPort being rendered (not null) | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void controlRender(RenderManager rm, ViewPort vp) { | ||||||
|  |         // No rendering-related code needed | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,36 @@ | |||||||
|  | package pp.battleship.client.gui; | ||||||
|  |  | ||||||
|  | import com.simsilica.lemur.Slider; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The VolumeSlider class represents the Volume Slider in the Menu. | ||||||
|  |  * It extends the Slider class and provides functionalities for setting the music volume, | ||||||
|  |  * with the help of the Slider in the GUI  | ||||||
|  |  */ | ||||||
|  | public class VolumeSlider extends Slider { | ||||||
|  |  | ||||||
|  |     private final GameMusic music; | ||||||
|  |     private double vol; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Constructs the Volume Slider for the Menu dialog | ||||||
|  |      * @param music the music instance | ||||||
|  |      */ | ||||||
|  |     public VolumeSlider(GameMusic music) { | ||||||
|  |         super(); | ||||||
|  |         this.music = music; | ||||||
|  |         vol = GameMusic.volumeInPreferences(); | ||||||
|  |         getModel().setPercent(vol); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * when triggered it updates the volume to the value set with the slider | ||||||
|  |      */ | ||||||
|  |     public void update() { | ||||||
|  |         if (vol != getModel().getPercent()) { | ||||||
|  |             vol = getModel().getPercent(); | ||||||
|  |             music.setVolume( (float) vol); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @@ -0,0 +1,182 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.server; | ||||||
|  |  | ||||||
|  | import com.jme3.network.ConnectionListener; | ||||||
|  | import com.jme3.network.HostedConnection; | ||||||
|  | import com.jme3.network.Message; | ||||||
|  | import com.jme3.network.MessageListener; | ||||||
|  | import com.jme3.network.Network; | ||||||
|  | import com.jme3.network.Server; | ||||||
|  | import com.jme3.network.serializing.Serializer; | ||||||
|  | import pp.battleship.BattleshipConfig; | ||||||
|  | import pp.battleship.game.server.Player; | ||||||
|  | import pp.battleship.game.server.ServerGameLogic; | ||||||
|  | import pp.battleship.game.server.ServerSender; | ||||||
|  | import pp.battleship.message.client.AnimationFinishedMessage; | ||||||
|  | import pp.battleship.message.client.ClientMessage; | ||||||
|  | import pp.battleship.message.client.MapMessage; | ||||||
|  | import pp.battleship.message.client.ShootMessage; | ||||||
|  | import pp.battleship.message.server.EffectMessage; | ||||||
|  | import pp.battleship.message.server.GameDetails; | ||||||
|  | import pp.battleship.message.server.ServerMessage; | ||||||
|  | import pp.battleship.message.server.StartBattleMessage; | ||||||
|  | import pp.battleship.model.Battleship; | ||||||
|  | import pp.battleship.model.IntPoint; | ||||||
|  | import pp.battleship.model.Shot; | ||||||
|  |  | ||||||
|  | import java.io.File; | ||||||
|  | import java.io.FileInputStream; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.lang.System.Logger; | ||||||
|  | import java.lang.System.Logger.Level; | ||||||
|  | import java.util.concurrent.BlockingQueue; | ||||||
|  | import java.util.concurrent.LinkedBlockingQueue; | ||||||
|  | import java.util.logging.LogManager; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Server implementing the visitor pattern as MessageReceiver for ClientMessages | ||||||
|  |  */ | ||||||
|  | public class BattleshipServer implements MessageListener<HostedConnection>, ConnectionListener, ServerSender { | ||||||
|  |     private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName()); | ||||||
|  |     private static final File CONFIG_FILE = new File("server.properties"); | ||||||
|  |  | ||||||
|  |     private final BattleshipConfig config = new BattleshipConfig(); | ||||||
|  |     private Server myServer; | ||||||
|  |     private final ServerGameLogic logic; | ||||||
|  |     private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>(); | ||||||
|  |  | ||||||
|  |     static { | ||||||
|  |         // Configure logging | ||||||
|  |         LogManager manager = LogManager.getLogManager(); | ||||||
|  |         try { | ||||||
|  |             manager.readConfiguration(new FileInputStream("logging.properties")); | ||||||
|  |             LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS | ||||||
|  |         } | ||||||
|  |         catch (IOException e) { | ||||||
|  |             LOGGER.log(Level.INFO, e.getMessage()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Starts the Battleships server. | ||||||
|  |      */ | ||||||
|  |     public static void main(String[] args) { | ||||||
|  |         new BattleshipServer().run(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates the server. | ||||||
|  |      */ | ||||||
|  |     BattleshipServer() { | ||||||
|  |         config.readFromIfExists(CONFIG_FILE); | ||||||
|  |         LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS | ||||||
|  |         logic = new ServerGameLogic(this, config); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void run() { | ||||||
|  |         startServer(); | ||||||
|  |         while (true) | ||||||
|  |             processNextMessage(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void startServer() { | ||||||
|  |         try { | ||||||
|  |             LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS | ||||||
|  |             myServer = Network.createServer(config.getPort()); | ||||||
|  |             initializeSerializables(); | ||||||
|  |             myServer.start(); | ||||||
|  |             registerListeners(); | ||||||
|  |             LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS | ||||||
|  |         } | ||||||
|  |         catch (IOException e) { | ||||||
|  |             LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS | ||||||
|  |             exit(1); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void processNextMessage() { | ||||||
|  |         try { | ||||||
|  |             pendingMessages.take().process(logic); | ||||||
|  |         } | ||||||
|  |         catch (InterruptedException ex) { | ||||||
|  |             LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS | ||||||
|  |             Thread.currentThread().interrupt(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void initializeSerializables() { | ||||||
|  |         Serializer.registerClass(GameDetails.class); | ||||||
|  |         Serializer.registerClass(StartBattleMessage.class); | ||||||
|  |         Serializer.registerClass(MapMessage.class); | ||||||
|  |         Serializer.registerClass(ShootMessage.class); | ||||||
|  |         Serializer.registerClass(EffectMessage.class); | ||||||
|  |         Serializer.registerClass(AnimationFinishedMessage.class); | ||||||
|  |         Serializer.registerClass(Battleship.class); | ||||||
|  |         Serializer.registerClass(IntPoint.class); | ||||||
|  |         Serializer.registerClass(Shot.class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void registerListeners() { | ||||||
|  |         myServer.addMessageListener(this, MapMessage.class); | ||||||
|  |         myServer.addMessageListener(this, ShootMessage.class); | ||||||
|  |         myServer.addMessageListener(this, AnimationFinishedMessage.class); | ||||||
|  |         myServer.addConnectionListener(this); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void messageReceived(HostedConnection source, Message message) { | ||||||
|  |         LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS | ||||||
|  |         if (message instanceof ClientMessage clientMessage) | ||||||
|  |             pendingMessages.add(new ReceivedMessage(clientMessage, source.getId())); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void connectionAdded(Server server, HostedConnection hostedConnection) { | ||||||
|  |         LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS | ||||||
|  |         logic.addPlayer(hostedConnection.getId()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void connectionRemoved(Server server, HostedConnection hostedConnection) { | ||||||
|  |         LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS | ||||||
|  |         final Player player = logic.getPlayerById(hostedConnection.getId()); | ||||||
|  |         if (player == null) | ||||||
|  |             LOGGER.log(Level.INFO, "closed connection does not belong to an active player"); //NON-NLS | ||||||
|  |         else { //NON-NLS | ||||||
|  |             LOGGER.log(Level.INFO, "closed connection belongs to {0}", player); //NON-NLS | ||||||
|  |             exit(0); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void exit(int exitValue) { //NON-NLS | ||||||
|  |         LOGGER.log(Level.INFO, "close request"); //NON-NLS | ||||||
|  |         if (myServer != null) | ||||||
|  |             for (HostedConnection client : myServer.getConnections()) //NON-NLS | ||||||
|  |                 if (client != null) client.close("Game over"); //NON-NLS | ||||||
|  |         System.exit(exitValue); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Send the specified message to the specified connection. | ||||||
|  |      * | ||||||
|  |      * @param id      the connection id | ||||||
|  |      * @param message the message | ||||||
|  |      */ | ||||||
|  |     public void send(int id, ServerMessage message) { | ||||||
|  |         if (myServer == null || !myServer.isRunning()) { | ||||||
|  |             LOGGER.log(Level.ERROR, "no server running when trying to send {0}", message); //NON-NLS | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         final HostedConnection connection = myServer.getConnection(id); | ||||||
|  |         if (connection != null) | ||||||
|  |             connection.send(message); | ||||||
|  |         else | ||||||
|  |             LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,17 @@ | |||||||
|  | //////////////////////////////////////// | ||||||
|  | // Programming project code | ||||||
|  | // UniBw M, 2022, 2023, 2024 | ||||||
|  | // www.unibw.de/inf2 | ||||||
|  | // (c) Mark Minas (mark.minas@unibw.de) | ||||||
|  | //////////////////////////////////////// | ||||||
|  |  | ||||||
|  | package pp.battleship.server; | ||||||
|  |  | ||||||
|  | import pp.battleship.message.client.ClientInterpreter; | ||||||
|  | import pp.battleship.message.client.ClientMessage; | ||||||
|  |  | ||||||
|  | record ReceivedMessage(ClientMessage message, int from) { | ||||||
|  |     void process(ClientInterpreter interpreter) { | ||||||
|  |         message.accept(interpreter, from); | ||||||
|  |     } | ||||||
|  | } | ||||||
| After Width: | Height: | Size: 16 KiB | 
| After Width: | Height: | Size: 46 KiB | 
| After Width: | Height: | Size: 63 KiB | 
| After Width: | Height: | Size: 2.0 KiB | 
| After Width: | Height: | Size: 16 KiB | 
| After Width: | Height: | Size: 32 KiB | 
| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 28 KiB | 
| After Width: | Height: | Size: 16 KiB | 
| After Width: | Height: | Size: 168 KiB | 
| After Width: | Height: | Size: 56 KiB | 
| After Width: | Height: | Size: 166 KiB | 
| After Width: | Height: | Size: 98 KiB | 
| After Width: | Height: | Size: 35 KiB | 
| After Width: | Height: | Size: 54 KiB | 
| After Width: | Height: | Size: 9.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Projekte/battleship/client/src/main/resources/Models/CV/CV.j3o
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 66 KiB | 
| After Width: | Height: | Size: 3.2 KiB | 
| After Width: | Height: | Size: 30 KiB | 
| After Width: | Height: | Size: 10 KiB | 
| After Width: | Height: | Size: 9.4 KiB | 
| After Width: | Height: | Size: 264 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Projekte/battleship/client/src/main/resources/Models/CV/_2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 80 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Projekte/battleship/client/src/main/resources/Models/CV/_6.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 91 KiB | 
| After Width: | Height: | Size: 33 KiB | 
| After Width: | Height: | Size: 98 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 78 B | 
| After Width: | Height: | Size: 79 B | 
| After Width: | Height: | Size: 96 KiB |