Compare commits
	
		
			975 Commits
		
	
	
		
			b_Grigench
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b7a3790f81 | ||
| 
						 | 
					56112f01bb | ||
| 
						 | 
					75ce695029 | ||
| 
						 | 
					9af54051da | ||
| 
						 | 
					ccf77f052f | ||
| 
						 | 
					0d3e93b681 | ||
| 
						 | 
					ab4c7159f3 | ||
| 
						 | 
					61ea4c3276 | ||
| 
						 | 
					e8506e607c | ||
| 
						 | 
					0c46477f01 | ||
| 
						 | 
					3ba1c62685 | ||
| 
						 | 
					82aa9f6a51 | ||
| 
						 | 
					9c9702b2c5 | ||
| 
						 | 
					d6ae8f3bdc | ||
| 
						 | 
					a3e6db6573 | ||
| 
						 | 
					c7cba412fe | ||
| 
						 | 
					caa370fec2 | ||
| 
						 | 
					5c61c8c263 | ||
| 
						 | 
					d8eefeb348 | ||
| 
						 | 
					3f07400cf8 | ||
| 
						 | 
					e3ec2aaba8 | ||
| 
						 | 
					70e31e15c7 | ||
| 
						 | 
					b891e78647 | ||
| 
						 | 
					ff41183643 | ||
| 
						 | 
					3658a91b7b | ||
| 
						 | 
					1e5c5bd5eb | ||
| 
						 | 
					23d4bf31a7 | ||
| 
						 | 
					5826d93be4 | ||
| 
						 | 
					9c3b949e5a | ||
| 
						 | 
					81223653f9 | ||
| 
						 | 
					28c06c931e | ||
| 
						 | 
					f93da332b3 | ||
| 
						 | 
					379e4bcec1 | ||
| 
						 | 
					dc7dae5db9 | ||
| 
						 | 
					702154c018 | ||
| 
						 | 
					032cd76ec2 | ||
| 
						 | 
					5c8dce0626 | ||
| 
						 | 
					911ab42101 | ||
| 
						 | 
					ea3f4df636 | ||
| 
						 | 
					065a626307 | ||
| 
						 | 
					2ec9b3a246 | ||
| 
						 | 
					48d6516073 | ||
| 
						 | 
					0d4685f3c2 | ||
| 
						 | 
					d12b0b6a77 | ||
| 
						 | 
					4dda9229b3 | ||
| 
						 | 
					e6453b2f1b | ||
| 
						 | 
					af8ab21e5f | ||
| 
						 | 
					d21e963d33 | ||
| 
						 | 
					1f3b07709c | ||
| 
						 | 
					a6215b9986 | ||
| 
						 | 
					6381d7b3b9 | ||
| 
						 | 
					176affa9c5 | ||
| 
						 | 
					67ea9ede18 | ||
| 
						 | 
					173cbe31e5 | ||
| 
						 | 
					c13152bb29 | ||
| 
						 | 
					a3d0056f7f | ||
| 
						 | 
					06dcc4c0e4 | ||
| 
						 | 
					61e168cefb | ||
| 
						 | 
					cecd7e1a23 | ||
| 
						 | 
					f9772732c4 | ||
| 
						 | 
					f09766eb42 | ||
| 
						 | 
					62e3dd7932 | ||
| 
						 | 
					d80499a18a | ||
| 
						 | 
					4b665c4cf2 | ||
| 
						 | 
					3048caf5c9 | ||
| 
						 | 
					b8df688ba3 | ||
| 
						 | 
					8201ab4b63 | ||
| 
						 | 
					14a407b7d5 | ||
| 
						 | 
					170a282077 | ||
| 
						 | 
					5854609b12 | ||
| 
						 | 
					ea25f66dae | ||
| 
						 | 
					c069a61f2d | ||
| 
						 | 
					07029407d8 | ||
| 
						 | 
					42a73ceb24 | ||
| 
						 | 
					42283f29a0 | ||
| 
						 | 
					574f8bb681 | ||
| 
						 | 
					c1641cdac1 | ||
| 
						 | 
					f251bad97f | ||
| 
						 | 
					f8a021b310 | ||
| 
						 | 
					aa44b84648 | ||
| 
						 | 
					d8816be811 | ||
| 
						 | 
					e61265fc99 | ||
| 
						 | 
					9e2a0819ca | ||
| 
						 | 
					cc756453a1 | ||
| 
						 | 
					f23f1f5abc | ||
| 
						 | 
					cc8b2abbde | ||
| 
						 | 
					8d7701cbf2 | ||
| 
						 | 
					874bba0fe9 | ||
| 
						 | 
					3b73d1229b | ||
| 
						 | 
					9f1dafece2 | ||
| 
						 | 
					997b31eba2 | ||
| 
						 | 
					148b769232 | ||
| 
						 | 
					f0f4e0eb5e | ||
| 
						 | 
					bda1a40cdb | ||
| 
						 | 
					0c8cb92fe8 | ||
| 
						 | 
					e952233d20 | ||
| 
						 | 
					9f8fd9c22f | ||
| 
						 | 
					0432dd6bd7 | ||
| 
						 | 
					4d31fc098b | ||
| 
						 | 
					40a61daa58 | ||
| 
						 | 
					6e54ad0196 | ||
| 
						 | 
					11fa303f1b | ||
| 
						 | 
					d65111680b | ||
| 
						 | 
					485b8f36d7 | ||
| 
						 | 
					4eee62079e | ||
| 
						 | 
					888b52f314 | ||
| 
						 | 
					bc2c80dd27 | ||
| 
						 | 
					50f0cdfce6 | ||
| 
						 | 
					0c7030659a | ||
| 
						 | 
					30997eb571 | ||
| 
						 | 
					ea1978d5a8 | ||
| 
						 | 
					1ead0e2e9e | ||
| 
						 | 
					70c821f2ac | ||
| 
						 | 
					8867e8156f | ||
| 
						 | 
					36c2f2efec | ||
| 
						 | 
					b8d992590f | ||
| 
						 | 
					a86a146091 | ||
| 
						 | 
					3e060897fc | ||
| 
						 | 
					fe95a7c159 | ||
| 
						 | 
					d60c0347cc | ||
| 
						 | 
					bbde17aa27 | ||
| 
						 | 
					38c865d135 | ||
| 
						 | 
					91d4718179 | ||
| 
						 | 
					bb1bf22713 | ||
| 
						 | 
					380341114c | ||
| 
						 | 
					f56eb8cd60 | ||
| 
						 | 
					3797da1246 | ||
| 
						 | 
					e79a1168c0 | ||
| 
						 | 
					6ab8f2d90d | ||
| 
						 | 
					ece249cf66 | ||
| 
						 | 
					e95f0866d5 | ||
| 
						 | 
					0fce2fb5d2 | ||
| 
						 | 
					0010717411 | ||
| 
						 | 
					cf9acf981f | ||
| 
						 | 
					e2a4f7f85c | ||
| 
						 | 
					1ffabe6b19 | ||
| 
						 | 
					0ba5a2f9fd | ||
| 
						 | 
					d0afaa57f5 | ||
| 
						 | 
					00a79ddce3 | ||
| 
						 | 
					bce6a1e0eb | ||
| 
						 | 
					37dcc0122c | ||
| 
						 | 
					4007036cb2 | ||
| 
						 | 
					5b958740c4 | ||
| 
						 | 
					06cb25b6a9 | ||
| 
						 | 
					afec74416f | ||
| 
						 | 
					ceb9e48f55 | ||
| 
						 | 
					f8179f191f | ||
| 
						 | 
					ec77b9a6ef | ||
| 
						 | 
					2d01e4b31b | ||
| 
						 | 
					0be25ecb29 | ||
| 
						 | 
					1210324194 | ||
| 
						 | 
					569ce39d2a | ||
| 
						 | 
					80989310e5 | ||
| 
						 | 
					a2f57bcaae | ||
| 
						 | 
					68702f11a4 | ||
| 
						 | 
					13948ec4bb | ||
| 
						 | 
					570c915964 | ||
| 
						 | 
					ac719c55e6 | ||
| 
						 | 
					189e8a7ec3 | ||
| 
						 | 
					5d2ece41b7 | ||
| 
						 | 
					7f92d8183b | ||
| 
						 | 
					6bc769f5bb | ||
| 
						 | 
					a21794ddc1 | ||
| 
						 | 
					e699f4556e | ||
| 
						 | 
					a56f68efdc | ||
| 
						 | 
					55328e9e69 | ||
| 
						 | 
					50dc634a94 | ||
| 
						 | 
					d37b9ee269 | ||
| 
						 | 
					3937b75bf7 | ||
| 
						 | 
					7d5b8a6b13 | ||
| 
						 | 
					39d5b43cb8 | ||
| 
						 | 
					de0f04b5b7 | ||
| 
						 | 
					6a4bdfa455 | ||
| 
						 | 
					db16b0d8b6 | ||
| 
						 | 
					08db05a7d6 | ||
| 
						 | 
					e47fcd1643 | ||
| 
						 | 
					dfd361d8be | ||
| 
						 | 
					bf3d800c10 | ||
| 
						 | 
					8943dfb15e | ||
| 
						 | 
					64f11eb99b | ||
| 
						 | 
					0e9ff609ec | ||
| 
						 | 
					d37db68838 | ||
| 
						 | 
					25fff99ff0 | ||
| 
						 | 
					2dbdb1e17b | ||
| 
						 | 
					f954a24b32 | ||
| 
						 | 
					c7a05011ff | ||
| 
						 | 
					15b7aa9e8c | ||
| 
						 | 
					84a50ec215 | ||
| 
						 | 
					e05b057190 | ||
| 
						 | 
					9ea6837e89 | ||
| 
						 | 
					a26a3d171f | ||
| 
						 | 
					a2e85cebc9 | ||
| 
						 | 
					3fe2d90d75 | ||
| 
						 | 
					4181456598 | ||
| 
						 | 
					6f34b1fe33 | ||
| 
						 | 
					659fef6c50 | ||
| 
						 | 
					9c7a13c568 | ||
| 
						 | 
					14699e3edf | ||
| 
						 | 
					961da990ce | ||
| 
						 | 
					be8d4b2d6e | ||
| 
						 | 
					38687b6d25 | ||
| 
						 | 
					c038073bad | ||
| 
						 | 
					5bccee96a2 | ||
| 
						 | 
					ecb751824c | ||
| 
						 | 
					a7969d7a68 | ||
| 
						 | 
					3a1b17ed01 | ||
| 
						 | 
					3dd6fc9f37 | ||
| 
						 | 
					3ae9028b82 | ||
| 
						 | 
					536c14bf7c | ||
| 
						 | 
					fb3a663db1 | ||
| 
						 | 
					b91d448ee6 | ||
| 
						 | 
					935577462b | ||
| 
						 | 
					f79d590620 | ||
| 
						 | 
					69d160d0fd | ||
| 
						 | 
					de592cfa48 | ||
| 
						 | 
					8922cb8f1c | ||
| 
						 | 
					f10df60ad3 | ||
| 
						 | 
					1c87b566e9 | ||
| 
						 | 
					6ecf5a66bf | ||
| 
						 | 
					3f93d6e569 | ||
| 
						 | 
					cc72b82be9 | ||
| 
						 | 
					41ac04f69d | ||
| 
						 | 
					cb60cb1c42 | ||
| 
						 | 
					61d67fd833 | ||
| 
						 | 
					4ca7c38170 | ||
| 
						 | 
					e457fe23d4 | ||
| 
						 | 
					b86aeb63e6 | ||
| 
						 | 
					daa8c0bf9d | ||
| 
						 | 
					6487bafed1 | ||
| 
						 | 
					8af3b2d9d4 | ||
| 
						 | 
					d38690ea48 | ||
| 
						 | 
					daa7d31bdd | ||
| 
						 | 
					5e79a4a64c | ||
| 
						 | 
					8e2fc6c1a1 | ||
| 
						 | 
					5a12d8e96f | ||
| 
						 | 
					3af6e94920 | ||
| 
						 | 
					4efc557849 | ||
| 
						 | 
					1646526ce1 | ||
| 
						 | 
					904aa17358 | ||
| 
						 | 
					f4e774ee5b | ||
| 
						 | 
					76b3e18dbe | ||
| 
						 | 
					d4cbd0dda6 | ||
| 
						 | 
					7a189a98e3 | ||
| 
						 | 
					00902d2e6b | ||
| 
						 | 
					7256cde020 | ||
| 
						 | 
					b817af29b5 | ||
| 
						 | 
					fa18ae4280 | ||
| 
						 | 
					521c7439c2 | ||
| 
						 | 
					84c5553154 | ||
| 
						 | 
					2c94737023 | ||
| 
						 | 
					d3d75d7f49 | ||
| 
						 | 
					5e67b2d0c7 | ||
| 
						 | 
					992efd403d | ||
| 
						 | 
					2d7fddf09a | ||
| 
						 | 
					f7c886f084 | ||
| 
						 | 
					a1d85177c6 | ||
| 
						 | 
					e27d325faa | ||
| 
						 | 
					bd98f301c8 | ||
| 
						 | 
					a7850f7d43 | ||
| 
						 | 
					3040595193 | ||
| 
						 | 
					5d45cf2934 | ||
| 
						 | 
					a5d949b7e5 | ||
| 
						 | 
					f4a224621e | ||
| 
						 | 
					876c238db3 | ||
| 
						 | 
					b61b8214fe | ||
| 
						 | 
					5dc3124533 | ||
| 
						 | 
					c25e17fd90 | ||
| 
						 | 
					5e27473875 | ||
| 
						 | 
					1be2d6aa13 | ||
| 
						 | 
					ab9de3acf8 | ||
| 
						 | 
					2ab19d0fc8 | ||
| 
						 | 
					f489357bbb | ||
| 
						 | 
					34cde15a0d | ||
| 
						 | 
					1e52df2812 | ||
| 
						 | 
					884c5afc4e | ||
| 
						 | 
					d6e44c2d29 | ||
| 
						 | 
					d593233fa3 | ||
| 
						 | 
					d1b4aa9dda | ||
| 
						 | 
					25b6480c42 | ||
| 
						 | 
					3dd81ea02c | ||
| 
						 | 
					e6dbedab0f | ||
| 
						 | 
					f5b0481d3c | ||
| 
						 | 
					f9f381ac2d | ||
| 
						 | 
					ee94d901f4 | ||
| 
						 | 
					bca02bfe4b | ||
| 
						 | 
					acdf5ec6a9 | ||
| 
						 | 
					41d6f70d51 | ||
| 
						 | 
					4c064cb615 | ||
| 
						 | 
					121f47d070 | ||
| 
						 | 
					ae436589a2 | ||
| 
						 | 
					bc399b1bf9 | ||
| 
						 | 
					98a6f2e689 | ||
| 
						 | 
					9a07375fed | ||
| 
						 | 
					498c2eb054 | ||
| 
						 | 
					ce55ca8bb5 | ||
| 
						 | 
					9c729059bf | ||
| 
						 | 
					3b7ef37364 | ||
| 
						 | 
					ec295c94f1 | ||
| 
						 | 
					adfe2b94b8 | ||
| 
						 | 
					69108063a0 | ||
| 
						 | 
					16e7488fae | ||
| 
						 | 
					c9c99709ba | ||
| 
						 | 
					e069017375 | ||
| 
						 | 
					8b27ccce22 | ||
| 
						 | 
					8c22d935a9 | ||
| 
						 | 
					c8d7d91de0 | ||
| 
						 | 
					389d1b6056 | ||
| 
						 | 
					4430b37581 | ||
| 
						 | 
					e5abcbdc8c | ||
| 
						 | 
					e14b8cb510 | ||
| 
						 | 
					bf84bfa0f9 | ||
| 
						 | 
					0c49d7ed1c | ||
| 
						 | 
					2ba6a22422 | ||
| 
						 | 
					c37bac4614 | ||
| 
						 | 
					06b37584cb | ||
| 
						 | 
					0c42a2df88 | ||
| 
						 | 
					d75d704878 | ||
| 
						 | 
					6d3c733f91 | ||
| 
						 | 
					f96da2c46c | ||
| 
						 | 
					1a079dad44 | ||
| 
						 | 
					32f49a6181 | ||
| 
						 | 
					525809899e | ||
| 
						 | 
					fd9708752c | ||
| 
						 | 
					5a9f7a8118 | ||
| 
						 | 
					236d3db930 | ||
| 
						 | 
					29c6b13300 | ||
| 
						 | 
					6059e93276 | ||
| 
						 | 
					f2eeb6dab4 | ||
| 
						 | 
					2e1fe3c050 | ||
| 
						 | 
					2ac2de645b | ||
| 
						 | 
					d39f85fbe9 | ||
| 
						 | 
					960a57caba | ||
| 
						 | 
					36631df2e9 | ||
| 
						 | 
					df27c23cd5 | ||
| 
						 | 
					acd64d1507 | ||
| 
						 | 
					76f86c8a66 | ||
| 
						 | 
					308b592b65 | ||
| 
						 | 
					c4e7fb1d41 | ||
| 
						 | 
					aacc0440b3 | ||
| 
						 | 
					43c0e3bcc7 | ||
| 
						 | 
					95635f5fb7 | ||
| 
						 | 
					e337b1f888 | ||
| 
						 | 
					0237bcc4be | ||
| 
						 | 
					26836d16cc | ||
| 
						 | 
					a6c8cc33f4 | ||
| 
						 | 
					7f5f4b8c68 | ||
| 
						 | 
					7f3483aa6b | ||
| 
						 | 
					8422b7be1e | ||
| 
						 | 
					f0b23ab9c2 | ||
| 
						 | 
					78f1dbb3d3 | ||
| 
						 | 
					4904b32ea3 | ||
| 
						 | 
					b00219c4fb | ||
| 
						 | 
					12cf5f3e71 | ||
| 
						 | 
					77b0207214 | ||
| 
						 | 
					a18165bc02 | ||
| 
						 | 
					9e758e4417 | ||
| 
						 | 
					62ceff822f | ||
| 
						 | 
					33afc4ab3b | ||
| 
						 | 
					322d539cfd | ||
| 
						 | 
					9c4f2387ee | ||
| 
						 | 
					9d1430e488 | ||
| 
						 | 
					dd2146d417 | ||
| 
						 | 
					d9ad0f0a4b | ||
| 
						 | 
					0368ec8541 | ||
| 
						 | 
					72f0bc5a2f | ||
| 
						 | 
					23ae4a3080 | ||
| 
						 | 
					765b1884fe | ||
| 
						 | 
					e3febd6ba1 | ||
| 
						 | 
					1a562a8d38 | ||
| 
						 | 
					39ed4238b5 | ||
| 
						 | 
					620063e894 | ||
| 
						 | 
					2d0788eb72 | ||
| 
						 | 
					8470a96908 | ||
| 
						 | 
					a6205c982a | ||
| 
						 | 
					58b9298c91 | ||
| 
						 | 
					1e6856744b | ||
| 
						 | 
					f713e00c36 | ||
| 
						 | 
					81ae896ae8 | ||
| 
						 | 
					5b55d39c9a | ||
| 
						 | 
					f36e2ff7bb | ||
| 
						 | 
					16afe95aa6 | ||
| 
						 | 
					55d398b428 | ||
| 
						 | 
					affa2ecd7e | ||
| 
						 | 
					0a1bd1f503 | ||
| 
						 | 
					a2867fc88a | ||
| 
						 | 
					c6761d91d1 | ||
| 
						 | 
					5708ee6ffe | ||
| 
						 | 
					d61b68aa41 | ||
| 
						 | 
					e98418b274 | ||
| 
						 | 
					66dc9c02ea | ||
| 
						 | 
					dd95356abd | ||
| 
						 | 
					84776c71b2 | ||
| 
						 | 
					d07eee6251 | ||
| 
						 | 
					b601ff2cf7 | ||
| 
						 | 
					04119d2f3e | ||
| 
						 | 
					a92c06a70e | ||
| 
						 | 
					50f9c0ef0c | ||
| 
						 | 
					f7f246daaa | ||
| 
						 | 
					8a738a3633 | ||
| 
						 | 
					3a32a7ebf7 | ||
| 
						 | 
					6894802c00 | ||
| 
						 | 
					a09211da5f | ||
| 
						 | 
					c9c9c5dcf6 | ||
| 
						 | 
					1e46b1dc59 | ||
| 
						 | 
					6c74acc334 | ||
| 
						 | 
					421231aa12 | ||
| 
						 | 
					d8bb458e9c | ||
| 
						 | 
					f0080118d0 | ||
| 
						 | 
					8a438ab069 | ||
| 
						 | 
					0ce8184069 | ||
| 
						 | 
					2c524477d7 | ||
| 
						 | 
					587af466e8 | ||
| 
						 | 
					8d398450f1 | ||
| 
						 | 
					0a96dd6f9f | ||
| 
						 | 
					e8a556de27 | ||
| 
						 | 
					1214d3c87c | ||
| 
						 | 
					f2c34aee2d | ||
| 
						 | 
					2da1fec7dd | ||
| 
						 | 
					5d76a89b95 | ||
| 
						 | 
					60ebef3518 | ||
| 
						 | 
					a399b14291 | ||
| 
						 | 
					9d21e2ce87 | ||
| 
						 | 
					2255bfd648 | ||
| 
						 | 
					74194d8514 | ||
| 
						 | 
					cabd98a24a | ||
| 
						 | 
					f3816cb2a5 | ||
| 
						 | 
					997c4c589e | ||
| 
						 | 
					d14a0aef86 | ||
| 
						 | 
					ac5d7ed74b | ||
| 
						 | 
					f1124f3245 | ||
| 
						 | 
					fc4a357e9e | ||
| 
						 | 
					a8b02faa96 | ||
| 
						 | 
					4a7c23708c | ||
| 
						 | 
					4478291852 | ||
| 
						 | 
					0622c35303 | ||
| 
						 | 
					3b0cd9ebdb | ||
| 
						 | 
					e81aa67d36 | ||
| 
						 | 
					4fb848420b | ||
| 
						 | 
					07a833afe7 | ||
| 
						 | 
					6576250113 | ||
| 
						 | 
					154efccf31 | ||
| 
						 | 
					f90aed7bbb | ||
| 
						 | 
					3a86837307 | ||
| 
						 | 
					da0756452c | ||
| 
						 | 
					bfe8a20f92 | ||
| 
						 | 
					0e6a2499b7 | ||
| 
						 | 
					354cdc0a9c | ||
| 
						 | 
					00d86c5c10 | ||
| 
						 | 
					990e476753 | ||
| 
						 | 
					71fc08a05c | ||
| 
						 | 
					9e1ca584c7 | ||
| 
						 | 
					9199fbffd8 | ||
| 
						 | 
					5c71531277 | ||
| 
						 | 
					6f7c5346d2 | ||
| 
						 | 
					ef1ce63db6 | ||
| 
						 | 
					e8d1442e5b | ||
| 
						 | 
					bdacc4aad3 | ||
| 
						 | 
					44ef21e6af | ||
| 
						 | 
					8e8104b672 | ||
| 
						 | 
					de899cef35 | ||
| 
						 | 
					11d6dd4500 | ||
| 
						 | 
					d71f824ca6 | ||
| 
						 | 
					8e6cb27662 | ||
| 
						 | 
					29711d6210 | ||
| 
						 | 
					29d8e791f6 | ||
| 
						 | 
					4440341f79 | ||
| 
						 | 
					de5c8bf44c | ||
| 
						 | 
					ab5cece1b3 | ||
| 
						 | 
					b8ed5060d6 | ||
| 
						 | 
					c0b72ae4da | ||
| 
						 | 
					c1b4caa82b | ||
| 
						 | 
					a757158477 | ||
| 
						 | 
					964ff87b11 | ||
| 
						 | 
					7053b163e5 | ||
| 
						 | 
					81cb2f33ff | ||
| 
						 | 
					69865bb504 | ||
| 
						 | 
					db50986f3f | ||
| 
						 | 
					a0a088a0c4 | ||
| 
						 | 
					c4d11ff961 | ||
| 
						 | 
					bb51976127 | ||
| 
						 | 
					0db1f08f3c | ||
| 
						 | 
					336f1ec316 | ||
| 
						 | 
					a1e687912a | ||
| 
						 | 
					2248d044c1 | ||
| 
						 | 
					79bf1c16e8 | ||
| 
						 | 
					3353a890d3 | ||
| 
						 | 
					a012402a85 | ||
| 
						 | 
					5aaf8d4850 | ||
| 
						 | 
					35ab777f04 | ||
| 
						 | 
					c707abc465 | ||
| 
						 | 
					2a84e7cf65 | ||
| 
						 | 
					3a02edb944 | ||
| 
						 | 
					1870d4fe0e | ||
| 
						 | 
					5cf9746931 | ||
| 
						 | 
					5b9bc7aa36 | ||
| 
						 | 
					abe66aff5d | ||
| 
						 | 
					eea566cc8b | ||
| 
						 | 
					bd07a44607 | ||
| 
						 | 
					1f64676d31 | ||
| 
						 | 
					838f59b9aa | ||
| 
						 | 
					002a42be38 | ||
| 
						 | 
					a1d10521ac | ||
| 
						 | 
					4e6a272e7a | ||
| 
						 | 
					516848a67e | ||
| 
						 | 
					659d69d3eb | ||
| 
						 | 
					fb6cbeaaf5 | ||
| 
						 | 
					25f750c8b6 | ||
| 
						 | 
					3f49b432c4 | ||
| 
						 | 
					252c37ae9a | ||
| 
						 | 
					4566d4c9a8 | ||
| 
						 | 
					e9ba888651 | ||
| 
						 | 
					cbbb98037b | ||
| 
						 | 
					aa651ec62f | ||
| 
						 | 
					e163b87cc4 | ||
| 
						 | 
					1eb24b7a66 | ||
| 
						 | 
					492f7422f5 | ||
| 
						 | 
					27f8af70f5 | ||
| 
						 | 
					5910fcc701 | ||
| 
						 | 
					e94ed1e019 | ||
| 
						 | 
					7d54a906dd | ||
| 
						 | 
					5ae65921bf | ||
| 
						 | 
					468e4005dc | ||
| 
						 | 
					72321eab9a | ||
| 
						 | 
					951c92d890 | ||
| 
						 | 
					e87eb569c2 | ||
| 
						 | 
					baa967ecfc | ||
| 
						 | 
					8d39d61c71 | ||
| 
						 | 
					7fcee3cac0 | ||
| 
						 | 
					06d4b322e7 | ||
| 
						 | 
					1cf14f65bb | ||
| 
						 | 
					ebb9f839c7 | ||
| 
						 | 
					3eef4b2a02 | ||
| 
						 | 
					c1fa679261 | ||
| 
						 | 
					c48f924ead | ||
| 
						 | 
					73859d8c81 | ||
| 
						 | 
					1918aa80ff | ||
| 
						 | 
					d062b9dabc | ||
| 
						 | 
					7ddcdc3f48 | ||
| 
						 | 
					2cefc2c293 | ||
| 
						 | 
					c4304ae99a | ||
| 
						 | 
					005df94114 | ||
| 
						 | 
					44f893ccef | ||
| 
						 | 
					0d9a922f55 | ||
| 
						 | 
					289158cf35 | ||
| 
						 | 
					0a0762b6c9 | ||
| 
						 | 
					90a21087df | ||
| 
						 | 
					294ecdc56f | ||
| 
						 | 
					347ed152b8 | ||
| 
						 | 
					3daafde9f1 | ||
| 
						 | 
					09fda6b167 | ||
| 
						 | 
					f21fd9b0a6 | ||
| 
						 | 
					41e204b1f2 | ||
| 
						 | 
					208261c6bf | ||
| 
						 | 
					0411f2ead4 | ||
| 
						 | 
					eb819d4d5e | ||
| 
						 | 
					a2856bb157 | ||
| 
						 | 
					bcf17a0651 | ||
| 
						 | 
					eee6fccde5 | ||
| 
						 | 
					2118c72891 | ||
| 
						 | 
					cb362c4d0c | ||
| 
						 | 
					1bcb73cff7 | ||
| 
						 | 
					167d898a3c | ||
| 
						 | 
					6c136b78b8 | ||
| 
						 | 
					82234a7ff9 | ||
| 
						 | 
					92d2e74748 | ||
| 
						 | 
					4561a962d4 | ||
| 
						 | 
					5db7b64cef | ||
| 
						 | 
					bb1b721e77 | ||
| 
						 | 
					206cad2f79 | ||
| 
						 | 
					3717e7b794 | ||
| 
						 | 
					a15d7932d4 | ||
| 
						 | 
					d649e41e75 | ||
| 
						 | 
					6d0552f5a7 | ||
| 
						 | 
					47f9f46277 | ||
| 
						 | 
					b87f5de5fb | ||
| 
						 | 
					c434bcb684 | ||
| 
						 | 
					b9617c0a14 | ||
| 
						 | 
					0aa73ca6ee | ||
| 
						 | 
					fbc7246037 | ||
| 
						 | 
					2703084df1 | ||
| 
						 | 
					31b1d535ac | ||
| 
						 | 
					b3fb2f8fa4 | ||
| 
						 | 
					8fcac9b809 | ||
| 
						 | 
					6985d988f4 | ||
| 
						 | 
					6c3103b2ed | ||
| 
						 | 
					3658c88d72 | ||
| 
						 | 
					bfc812b003 | ||
| 
						 | 
					413f35d7bf | ||
| 
						 | 
					c5cb3d4dd0 | ||
| 
						 | 
					efb4439431 | ||
| 
						 | 
					601366f08d | ||
| 
						 | 
					918c1f2a8a | ||
| 
						 | 
					bf75b8afc9 | ||
| 
						 | 
					0b45727bd7 | ||
| 
						 | 
					a19ac2fc51 | ||
| 
						 | 
					79f0e55c52 | ||
| 
						 | 
					44378486d4 | ||
| 
						 | 
					149931d2cb | ||
| 
						 | 
					1250500558 | ||
| 
						 | 
					177bfe3001 | ||
| 
						 | 
					138444439d | ||
| 
						 | 
					702f96b8db | ||
| 
						 | 
					1b6407b75b | ||
| 
						 | 
					c649b8f3ae | ||
| 
						 | 
					41a669d44d | ||
| 
						 | 
					4b6b12c0ac | ||
| 
						 | 
					c1280ba089 | ||
| 
						 | 
					02f285a258 | ||
| 
						 | 
					bcb0ebc0f8 | ||
| 
						 | 
					a19902f819 | ||
| 
						 | 
					585687055c | ||
| 
						 | 
					a86319082c | ||
| 
						 | 
					7acc55fe25 | ||
| 
						 | 
					3e56de2a17 | ||
| 
						 | 
					468cd97374 | ||
| 
						 | 
					dee6bf9f9c | ||
| 
						 | 
					ea6431faa4 | ||
| 
						 | 
					f272fd6f08 | ||
| 
						 | 
					bfc74ee126 | ||
| 
						 | 
					d890d11978 | ||
| 
						 | 
					9af20346f1 | ||
| 
						 | 
					f853b0b54f | ||
| 
						 | 
					9dd2d3f58b | ||
| 
						 | 
					06e43903e6 | ||
| 
						 | 
					00a04c2403 | ||
| 
						 | 
					92cbd9202a | ||
| 
						 | 
					9a06afe998 | ||
| 
						 | 
					289390c528 | ||
| 
						 | 
					7712a23d00 | ||
| 
						 | 
					8137a727f5 | ||
| 
						 | 
					d904a28ee0 | ||
| 
						 | 
					2f2d9c7479 | ||
| 
						 | 
					c2b6e6e9e9 | ||
| 
						 | 
					697b974ce1 | ||
| 
						 | 
					f0cab64910 | ||
| 
						 | 
					adcf65d7aa | ||
| 
						 | 
					8f53b76a3e | ||
| 
						 | 
					eaef4b20ba | ||
| 
						 | 
					d3fe2fec14 | ||
| 
						 | 
					02d7ef1dd8 | ||
| 
						 | 
					23aa2db714 | ||
| 
						 | 
					e68369074f | ||
| 
						 | 
					aafc804c3f | ||
| 
						 | 
					bec199036c | ||
| 
						 | 
					a7e048d4b4 | ||
| 
						 | 
					bcd510804f | ||
| 
						 | 
					7319f7a62c | ||
| 
						 | 
					a1e51fb2f4 | ||
| 
						 | 
					f15d0eb5d8 | ||
| 
						 | 
					f484a4abc8 | ||
| 
						 | 
					5905a232a5 | ||
| 
						 | 
					12cb87ef70 | ||
| 
						 | 
					9cb7a156bb | ||
| 
						 | 
					76e2606847 | ||
| 
						 | 
					7762b97303 | ||
| 
						 | 
					eaf46f3e14 | ||
| 
						 | 
					92be52b62a | ||
| 
						 | 
					7a0a3589a5 | ||
| 
						 | 
					661c28f096 | ||
| 
						 | 
					4a94e85aac | ||
| 
						 | 
					799bd096a0 | ||
| 
						 | 
					5a68d3b146 | ||
| 
						 | 
					63fa692910 | ||
| 
						 | 
					638cb93d7b | ||
| 
						 | 
					133f900ec7 | ||
| 
						 | 
					33ddea4221 | ||
| 
						 | 
					977a7294ad | ||
| 
						 | 
					7db75a2ca1 | ||
| 
						 | 
					71d5701cc7 | ||
| 
						 | 
					42feca466d | ||
| 
						 | 
					95a1f8d858 | ||
| 
						 | 
					76757c19a9 | ||
| 
						 | 
					c3fdcf4dc7 | ||
| 
						 | 
					8ffab12f49 | ||
| 
						 | 
					da2b1af698 | ||
| 
						 | 
					8369797120 | ||
| 
						 | 
					ef450a23f5 | ||
| 
						 | 
					e5b007accd | ||
| 
						 | 
					17f0aa0209 | ||
| 
						 | 
					1d8eff8ea9 | ||
| 
						 | 
					ff31335a98 | ||
| 
						 | 
					3467dd2f04 | ||
| 
						 | 
					fb6cfaa518 | ||
| 
						 | 
					c08c81ea46 | ||
| 
						 | 
					e9d45f241f | ||
| 
						 | 
					bf38a7e00c | ||
| 
						 | 
					5d638a1da7 | ||
| 
						 | 
					b83279e835 | ||
| 
						 | 
					eb54cfbc80 | ||
| 
						 | 
					453aacfe1a | ||
| 
						 | 
					1513576291 | ||
| 
						 | 
					2732a89da6 | ||
| 
						 | 
					5b8032bed9 | ||
| 
						 | 
					21f98de3e6 | ||
| 
						 | 
					b099972656 | ||
| 
						 | 
					5206b03966 | ||
| 
						 | 
					eb703cbd2c | ||
| 
						 | 
					8c03b282b3 | ||
| 
						 | 
					a29e942191 | ||
| 
						 | 
					efc7a2f09d | ||
| 
						 | 
					121d668bf2 | ||
| 
						 | 
					ba5b9dc4b4 | ||
| 
						 | 
					772c7a51e0 | ||
| 
						 | 
					ed04bc1119 | ||
| 
						 | 
					7712ee2e7c | ||
| 
						 | 
					1d5733a4b9 | ||
| 
						 | 
					dfea7e8736 | ||
| 
						 | 
					2c81665f80 | ||
| 
						 | 
					150e4e4c22 | ||
| 
						 | 
					4ff84e64ed | ||
| 
						 | 
					e6fb78507c | ||
| 
						 | 
					9067a9b04c | ||
| 
						 | 
					6758abd60e | ||
| 
						 | 
					e70331d85d | ||
| 
						 | 
					c3ad8fe79a | ||
| 
						 | 
					26d2d0587d | ||
| 
						 | 
					00b3ef1d80 | ||
| 
						 | 
					789868863f | ||
| 
						 | 
					bdc527b83e | ||
| 
						 | 
					5ff56ed9d8 | ||
| 
						 | 
					81d037d232 | ||
| 
						 | 
					422e94ec48 | ||
| 
						 | 
					b3d754e77f | ||
| 
						 | 
					0297193be1 | ||
| 
						 | 
					a630ade2e1 | ||
| 
						 | 
					b197d70d44 | ||
| 
						 | 
					67d120c278 | ||
| 
						 | 
					d53067f21a | ||
| 
						 | 
					4313468a0c | ||
| 
						 | 
					0393e9b534 | ||
| 
						 | 
					f3bc6bc2f0 | ||
| 
						 | 
					12abe081c9 | ||
| 
						 | 
					02b536aa82 | ||
| 
						 | 
					99ffee749e | ||
| 
						 | 
					6f71a8b16d | ||
| 
						 | 
					a78b3acacd | ||
| 
						 | 
					0487ff0238 | ||
| 
						 | 
					6a85aca970 | ||
| 
						 | 
					c2cfd8c175 | ||
| 
						 | 
					af5d4a95cd | ||
| 
						 | 
					129ce54dd8 | ||
| 
						 | 
					8c8bd4db0d | ||
| 
						 | 
					1c222ce0e0 | ||
| 
						 | 
					f7a34d0d59 | ||
| 
						 | 
					cbb21e92f8 | ||
| 
						 | 
					4d6cd63a3d | ||
| 
						 | 
					36d31e99e9 | ||
| 
						 | 
					28a2c9a448 | ||
| 
						 | 
					dd8b16f1ac | ||
| 
						 | 
					be15b3bd63 | ||
| 
						 | 
					07f0f55192 | ||
| 
						 | 
					13690cf73d | ||
| 
						 | 
					220d8ff47e | ||
| 
						 | 
					67bb30d124 | ||
| 
						 | 
					184410ba31 | ||
| 
						 | 
					3147f5b7a3 | ||
| 
						 | 
					0e79f35cb0 | ||
| 
						 | 
					f024ba4866 | ||
| 
						 | 
					35270cce7b | ||
| 
						 | 
					b0761082ce | ||
| 
						 | 
					46182b33a8 | ||
| 
						 | 
					457023ad93 | ||
| 
						 | 
					c3055a0646 | ||
| 
						 | 
					ae1ec74056 | ||
| 
						 | 
					24cc81d9d4 | ||
| 
						 | 
					c25c12d0d6 | ||
| 
						 | 
					7b689d6bf6 | ||
| 
						 | 
					2099e02567 | ||
| 
						 | 
					7690340b8f | ||
| 
						 | 
					3dcdbdf489 | ||
| 
						 | 
					88cb87d4cd | ||
| 
						 | 
					827e7aac86 | ||
| 
						 | 
					eec68188ee | ||
| 
						 | 
					476ca82bda | ||
| 
						 | 
					3235a46788 | ||
| 
						 | 
					7da388af37 | ||
| 
						 | 
					304acf17a3 | ||
| 
						 | 
					9736dc828b | ||
| 
						 | 
					cbbef16374 | ||
| 
						 | 
					c9362a7a95 | ||
| 
						 | 
					0b9fc90274 | ||
| 
						 | 
					20c9000d56 | ||
| 
						 | 
					5a911326ba | ||
| 
						 | 
					6528e5c2b6 | ||
| 
						 | 
					621bb9efae | ||
| 
						 | 
					84c289cfd1 | ||
| 
						 | 
					f827757ad1 | ||
| 
						 | 
					e4a9b16fd5 | ||
| 
						 | 
					d63d0cc2a0 | ||
| 
						 | 
					a127ee524a | ||
| 
						 | 
					5c2df2430d | ||
| 
						 | 
					2e76c41d3a | ||
| 
						 | 
					c204a3a4cb | ||
| 
						 | 
					d794ba9d03 | ||
| 
						 | 
					e97147d0e9 | ||
| 
						 | 
					1b151aabdd | ||
| 
						 | 
					3e7d3dbc1b | ||
| 
						 | 
					7a482b74ab | ||
| 
						 | 
					5cc1888794 | ||
| 
						 | 
					9dd70a96a0 | ||
| 
						 | 
					80f5c4ce90 | ||
| 
						 | 
					7eafa3da39 | ||
| 
						 | 
					7178935553 | ||
| 
						 | 
					0b538efbfb | ||
| 
						 | 
					f2d5221328 | ||
| 
						 | 
					06195854d8 | ||
| 
						 | 
					321909387d | ||
| 
						 | 
					933c7ecad7 | ||
| 
						 | 
					806a149bfc | ||
| 
						 | 
					1803fb5549 | ||
| 
						 | 
					96489f2454 | ||
| 
						 | 
					439231aecf | ||
| 
						 | 
					9759b6871b | ||
| 
						 | 
					52d9fff493 | ||
| 
						 | 
					31b449662f | ||
| 
						 | 
					3be037d590 | ||
| 
						 | 
					8982662f5b | ||
| 
						 | 
					d70efb32ea | ||
| 
						 | 
					d15242f816 | ||
| 
						 | 
					4705512648 | ||
| 
						 | 
					7efb89c634 | ||
| 
						 | 
					15d1c36dad | ||
| 
						 | 
					ce528457a5 | ||
| 
						 | 
					2cfa328c4c | ||
| 
						 | 
					928304fb4b | ||
| 
						 | 
					aa88dff566 | ||
| 
						 | 
					f7bd9a0f38 | ||
| 
						 | 
					cc800a8dd7 | ||
| 
						 | 
					7c5720cb9d | ||
| 
						 | 
					b9986ded87 | ||
| 
						 | 
					fd09460d61 | ||
| 
						 | 
					e5dca013d1 | ||
| 
						 | 
					7bfc6e7c3e | ||
| 
						 | 
					1821a222e9 | ||
| 
						 | 
					2e302ddc75 | ||
| 
						 | 
					ed47883281 | ||
| 
						 | 
					599b3e8f47 | ||
| 
						 | 
					040b8830ab | ||
| 
						 | 
					6fa3e8c00d | ||
| 
						 | 
					83c79af31a | ||
| 
						 | 
					653c3fb962 | ||
| 
						 | 
					8932c8a8cd | ||
| 
						 | 
					10428deedd | ||
| 
						 | 
					a196d3d7f2 | ||
| 
						 | 
					4c078ab0e2 | ||
| 
						 | 
					2a97ede985 | ||
| 
						 | 
					56492cdda6 | ||
| 
						 | 
					a71619612a | ||
| 
						 | 
					f18a62b089 | ||
| 
						 | 
					c204984a74 | ||
| 
						 | 
					1d4048cf16 | ||
| 
						 | 
					2095ea5866 | ||
| 
						 | 
					f425eff26b | ||
| 
						 | 
					cd22ece485 | ||
| 
						 | 
					16dc28013e | ||
| 
						 | 
					9ab71bfaf6 | ||
| 
						 | 
					ccad47f95a | ||
| 
						 | 
					f830aec8ce | ||
| 
						 | 
					c8c0188452 | ||
| 
						 | 
					a5b7488e92 | ||
| 
						 | 
					11794b6ac7 | ||
| 
						 | 
					a8d80fd3f4 | ||
| 
						 | 
					806f0d7d9d | ||
| 
						 | 
					7368014b10 | ||
| 
						 | 
					ebdedc6494 | ||
| 
						 | 
					df2a7151f0 | ||
| 
						 | 
					00014eeb09 | ||
| 
						 | 
					89232901a7 | ||
| 
						 | 
					a79a315f83 | ||
| 
						 | 
					c470c205e4 | ||
| 
						 | 
					982ca00b55 | ||
| 
						 | 
					c1f4ea480c | ||
| 
						 | 
					a8a725611f | ||
| 
						 | 
					02ce0df614 | ||
| 
						 | 
					6e57e309cc | ||
| 
						 | 
					d87acd46cc | ||
| 
						 | 
					58395fe3cb | ||
| 
						 | 
					5f54eff038 | ||
| 
						 | 
					184b565526 | ||
| 
						 | 
					176efc2aca | ||
| 
						 | 
					bb491a2682 | ||
| 
						 | 
					51e83c5ae4 | ||
| 
						 | 
					47f0e44d0a | ||
| 
						 | 
					80fab1ffdc | ||
| 
						 | 
					03eef66332 | ||
| 
						 | 
					34415bc9f2 | ||
| 
						 | 
					030842d251 | ||
| 
						 | 
					a3a9f0d88d | ||
| 
						 | 
					6790be782e | ||
| 
						 | 
					56256fb9c0 | ||
| 
						 | 
					c6438d75cc | ||
| 
						 | 
					728530a8f2 | ||
| 
						 | 
					7c09107d28 | ||
| 
						 | 
					2399bf2678 | ||
| 
						 | 
					06d795b3e6 | ||
| 
						 | 
					8d9a923970 | ||
| 
						 | 
					c95beaeb14 | ||
| 
						 | 
					04501de11c | ||
| 
						 | 
					9cd9cc871c | ||
| 
						 | 
					f69a2a9fda | ||
| 
						 | 
					e83ed1c835 | ||
| 
						 | 
					f379a6b638 | ||
| 
						 | 
					90fb6e4133 | ||
| 
						 | 
					9662e1f684 | ||
| 
						 | 
					aae7ed9a87 | ||
| 
						 | 
					07bd7dfa3d | ||
| 
						 | 
					b15dd96a86 | ||
| 
						 | 
					ada90e787d | ||
| 
						 | 
					cbeb296a44 | ||
| 
						 | 
					a9fd13caab | ||
| 
						 | 
					a926554709 | ||
| 
						 | 
					bbd84dd961 | ||
| 
						 | 
					e22f675f8b | ||
| 
						 | 
					956e9dd2fd | ||
| 
						 | 
					020aa92cab | ||
| 
						 | 
					bb55ba7273 | ||
| 
						 | 
					a2c7b9e299 | ||
| 
						 | 
					0845aa80f9 | ||
| 
						 | 
					44b623f9fd | ||
| 
						 | 
					bf678000b8 | ||
| 
						 | 
					e128d9e4cc | ||
| 
						 | 
					8c10e69eff | ||
| 
						 | 
					6b2d775534 | ||
| 
						 | 
					b509ab772d | ||
| 
						 | 
					d0be65323e | ||
| 
						 | 
					81facb869f | ||
| 
						 | 
					b317bb957a | ||
| 
						 | 
					202cd7c0e6 | ||
| 
						 | 
					e345edc38e | ||
| 
						 | 
					133bffbd6b | ||
| 
						 | 
					2287fdc88f | ||
| 
						 | 
					976002ddd0 | ||
| 
						 | 
					205cb654c7 | ||
| 
						 | 
					d8544cc042 | ||
| 
						 | 
					0aa7e83d62 | ||
| 
						 | 
					289c285adf | ||
| 
						 | 
					a4ac52b591 | ||
| 
						 | 
					0bb4706fda | ||
| 
						 | 
					a3c5af58d7 | ||
| 
						 | 
					28960e8961 | ||
| 
						 | 
					bb7baf44bd | ||
| 
						 | 
					206c0d543e | ||
| 
						 | 
					a68ad80402 | ||
| 
						 | 
					1cca5a9c6b | ||
| 
						 | 
					ee59807395 | ||
| 
						 | 
					37511d6334 | ||
| 
						 | 
					c83baac763 | ||
| 
						 | 
					6c7b58b415 | ||
| 
						 | 
					33dbbdbe5c | ||
| 
						 | 
					5693817f9b | ||
| 
						 | 
					94ee786dc8 | ||
| 
						 | 
					92941f903c | ||
| 
						 | 
					499da6a7ad | ||
| 
						 | 
					9bafd749b6 | ||
| 
						 | 
					b71449b7b4 | ||
| 
						 | 
					dfcfe3351e | ||
| 
						 | 
					c7b65d949a | ||
| 
						 | 
					921933722a | ||
| 
						 | 
					7fa45f39c6 | ||
| 
						 | 
					6849a0a023 | ||
| 
						 | 
					e96cd40302 | ||
| 
						 | 
					d9a827697d | ||
| 
						 | 
					0f39ea0dcc | ||
| 
						 | 
					6e844287dd | ||
| 
						 | 
					3465cdaa19 | ||
| 
						 | 
					60adc39c8b | ||
| 
						 | 
					8502b97a95 | ||
| 
						 | 
					0e90041209 | ||
| 
						 | 
					f3f63a3e2c | ||
| 
						 | 
					8d02a144eb | ||
| 
						 | 
					af4caad6be | ||
| 
						 | 
					d1a343aaa9 | ||
| 
						 | 
					80d70e9a45 | ||
| 
						 | 
					516c7ca303 | ||
| 
						 | 
					a684405891 | 
							
								
								
									
										14
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,14 @@
 | 
			
		||||
root = true
 | 
			
		||||
 | 
			
		||||
[*]
 | 
			
		||||
charset = utf-8
 | 
			
		||||
end_of_line = lf
 | 
			
		||||
insert_final_newline = true
 | 
			
		||||
trim_trailing_whitespace = true
 | 
			
		||||
 | 
			
		||||
[*.java]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 4
 | 
			
		||||
 | 
			
		||||
[*.txt]
 | 
			
		||||
indent_style = tab
 | 
			
		||||
							
								
								
									
										59
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,59 @@
 | 
			
		||||
# Git configuration files
 | 
			
		||||
.gitattributes text
 | 
			
		||||
.gitignore text
 | 
			
		||||
.gitconfig text
 | 
			
		||||
.gitmodules text
 | 
			
		||||
 | 
			
		||||
# Documentation
 | 
			
		||||
*.md text
 | 
			
		||||
*.txt text
 | 
			
		||||
AUTHORS text
 | 
			
		||||
CHANGELOG text
 | 
			
		||||
CHANGES text
 | 
			
		||||
CONTRIBUTING text
 | 
			
		||||
COPYING text
 | 
			
		||||
INSTALL text
 | 
			
		||||
LICENSE text
 | 
			
		||||
NEWS text
 | 
			
		||||
TODO text
 | 
			
		||||
 | 
			
		||||
# Java sources
 | 
			
		||||
*.java          text diff=java
 | 
			
		||||
*.kt            text diff=kotlin
 | 
			
		||||
*.groovy        text diff=java
 | 
			
		||||
*.scala         text diff=java
 | 
			
		||||
*.gradle        text diff=java
 | 
			
		||||
*.gradle.kts    text diff=kotlin
 | 
			
		||||
 | 
			
		||||
# Normalize text files (Convert crlf => lf)
 | 
			
		||||
*.css           text diff=css
 | 
			
		||||
*.scss          text diff=css
 | 
			
		||||
*.sass          text
 | 
			
		||||
*.df            text
 | 
			
		||||
*.htm           text diff=html
 | 
			
		||||
*.html          text diff=html
 | 
			
		||||
*.js            text
 | 
			
		||||
*.mjs           text
 | 
			
		||||
*.cjs           text
 | 
			
		||||
*.jsp           text
 | 
			
		||||
*.jspf          text
 | 
			
		||||
*.jspx          text
 | 
			
		||||
*.properties    text
 | 
			
		||||
*.tld           text
 | 
			
		||||
*.tag           text
 | 
			
		||||
*.tagx          text
 | 
			
		||||
*.xml           text
 | 
			
		||||
 | 
			
		||||
# Binary files
 | 
			
		||||
# (binary is a macro for -text -diff)
 | 
			
		||||
*.class         binary
 | 
			
		||||
*.dll           binary
 | 
			
		||||
*.ear           binary
 | 
			
		||||
*.jar           binary
 | 
			
		||||
*.so            binary
 | 
			
		||||
*.war           binary
 | 
			
		||||
*.jks           binary
 | 
			
		||||
 | 
			
		||||
# Build tool wrappers
 | 
			
		||||
mvnw            text eol=lf
 | 
			
		||||
gradlew         text eol=lf
 | 
			
		||||
							
								
								
									
										53
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,26 +1,45 @@
 | 
			
		||||
# Gradle
 | 
			
		||||
 | 
			
		||||
.run/
 | 
			
		||||
.gradle
 | 
			
		||||
build
 | 
			
		||||
build/
 | 
			
		||||
#!gradle/wrapper/gradle-wrapper.jar
 | 
			
		||||
#!**/src/main/**/build/
 | 
			
		||||
#!**/src/test/**/build/
 | 
			
		||||
 | 
			
		||||
# VSC
 | 
			
		||||
bin
 | 
			
		||||
 | 
			
		||||
# IntelliJ
 | 
			
		||||
*.iml
 | 
			
		||||
### IntelliJ IDEA ###
 | 
			
		||||
.idea
 | 
			
		||||
out
 | 
			
		||||
.idea/modules.xml
 | 
			
		||||
.idea/jarRepositories.xml
 | 
			
		||||
.idea/compiler.xml
 | 
			
		||||
.idea/libraries/
 | 
			
		||||
*.iws
 | 
			
		||||
*.iml
 | 
			
		||||
*.ipr
 | 
			
		||||
out/
 | 
			
		||||
#!**/src/main/**/out/
 | 
			
		||||
#!**/src/test/**/out/
 | 
			
		||||
 | 
			
		||||
# Eclipse
 | 
			
		||||
### Eclipse ###
 | 
			
		||||
.apt_generated
 | 
			
		||||
.classpath
 | 
			
		||||
.factorypath
 | 
			
		||||
.project
 | 
			
		||||
.settings
 | 
			
		||||
.springBeans
 | 
			
		||||
.sts4-cache
 | 
			
		||||
bin/
 | 
			
		||||
#!**/src/main/**/bin/
 | 
			
		||||
#!**/src/test/**/bin/
 | 
			
		||||
 | 
			
		||||
# Libraries
 | 
			
		||||
*.so
 | 
			
		||||
*.dylib
 | 
			
		||||
*.dll
 | 
			
		||||
*.jar
 | 
			
		||||
*.class
 | 
			
		||||
### NetBeans ###
 | 
			
		||||
/nbproject/private/
 | 
			
		||||
/nbbuild/
 | 
			
		||||
/dist/
 | 
			
		||||
/nbdist/
 | 
			
		||||
/.nb-gradle/
 | 
			
		||||
 | 
			
		||||
### VS Code ###
 | 
			
		||||
.vscode/
 | 
			
		||||
 | 
			
		||||
### Mac OS ###
 | 
			
		||||
.DS_Store
 | 
			
		||||
!Projekte/gradle/wrapper/gradle-wrapper.jar
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								Dokumente/Abschlusspräsentation_v1.0.2.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										3
									
								
								Projekte/.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,3 +0,0 @@
 | 
			
		||||
*.bat           text eol=crlf
 | 
			
		||||
gradlew         text eol=lf
 | 
			
		||||
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
<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>
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
<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>
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
<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>
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
<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>
 | 
			
		||||
@@ -1,189 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
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'
 | 
			
		||||
}
 | 
			
		||||
@@ -1,73 +0,0 @@
 | 
			
		||||
########################################
 | 
			
		||||
## 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=2000
 | 
			
		||||
#
 | 
			
		||||
# 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
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "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"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "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"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,267 +0,0 @@
 | 
			
		||||
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.DataType;
 | 
			
		||||
import com.jme3.audio.AudioNode;
 | 
			
		||||
import pp.battleship.notification.GameEventListener;
 | 
			
		||||
import pp.battleship.notification.MusicEvent;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
import static pp.util.PreferencesUtils.getPreferences;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The BackgroundMusic class represents the background music in the Battleship game application.
 | 
			
		||||
 * It extends the AbstractAppState class and provides functionalities for playing the menu music,
 | 
			
		||||
 * game music, victory music, and defeat music.
 | 
			
		||||
 */
 | 
			
		||||
public class BackgroundMusic extends AbstractAppState implements GameEventListener {
 | 
			
		||||
    /**
 | 
			
		||||
     * Logger for the BackgroundMusic class.
 | 
			
		||||
     */
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(BackgroundMusic.class.getName());
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Preferences for storing music settings.
 | 
			
		||||
     */
 | 
			
		||||
    private static final Preferences PREFERENCES = getPreferences(BackgroundMusic.class);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Preference key for enabling/disabling music.
 | 
			
		||||
     */
 | 
			
		||||
    private static final String ENABLED_PREF = "enabled"; //NON-NLS
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Preference key for storing the volume level.
 | 
			
		||||
     */
 | 
			
		||||
    private static final String VOLUME_PREF = "volume"; //NON-NLS
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Path to the menu music file.
 | 
			
		||||
     */
 | 
			
		||||
    private static final String MENU_MUSIC_PATH = "Sound/Music/menu_music.ogg";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Path to the game music file.
 | 
			
		||||
     */
 | 
			
		||||
    private static final String GAME_MUSIC_PATH = "Sound/Music/pirates.ogg";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Path to the victory music file.
 | 
			
		||||
     */
 | 
			
		||||
    private static final String VICTORY_MUSIC_PATH = "Sound/Music/win_the_game.ogg";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Path to the defeat music file.
 | 
			
		||||
     */
 | 
			
		||||
    private static final String DEFEAT_MUSIC_PATH = "Sound/Music/defeat.ogg";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * AudioNode for the menu music.
 | 
			
		||||
     */
 | 
			
		||||
    private AudioNode menuMusic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * AudioNode for the game music.
 | 
			
		||||
     */
 | 
			
		||||
    private AudioNode gameMusic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * AudioNode for the victory music.
 | 
			
		||||
     */
 | 
			
		||||
    private AudioNode victoryMusic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * AudioNode for the defeat music.
 | 
			
		||||
     */
 | 
			
		||||
    private AudioNode defeatMusic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The currently playing AudioNode.
 | 
			
		||||
     */
 | 
			
		||||
    private AudioNode currentMusic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The volume level for the background music.
 | 
			
		||||
     */
 | 
			
		||||
    private float volume;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if music is enabled in the preferences.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@code true} if music is enabled, {@code false} otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean enabledInPreferences() {
 | 
			
		||||
        return PREFERENCES.getBoolean(ENABLED_PREF, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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, "Music enabled: {0}", enabled); //NON-NLS
 | 
			
		||||
        PREFERENCES.putBoolean(ENABLED_PREF, enabled);
 | 
			
		||||
        playCurrentMusic();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the music 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) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "Initializing background music"); //NON-NLS
 | 
			
		||||
        super.initialize(stateManager, app);
 | 
			
		||||
        menuMusic = loadMusic(app, MENU_MUSIC_PATH);
 | 
			
		||||
        gameMusic = loadMusic(app, GAME_MUSIC_PATH);
 | 
			
		||||
        victoryMusic = loadMusic(app, VICTORY_MUSIC_PATH);
 | 
			
		||||
        defeatMusic = loadMusic(app, DEFEAT_MUSIC_PATH);
 | 
			
		||||
        currentMusic = menuMusic;
 | 
			
		||||
        playCurrentMusic();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a music file and initializes an AudioNode with the specified settings.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The application instance.
 | 
			
		||||
     * @param name The name of the music file to load.
 | 
			
		||||
     * @return The initialized AudioNode, or {@code null} if the file could not be loaded.
 | 
			
		||||
     */
 | 
			
		||||
    private AudioNode loadMusic(Application app, String name) {
 | 
			
		||||
        try {
 | 
			
		||||
            this.volume = PREFERENCES.getFloat(VOLUME_PREF, 0.5f);
 | 
			
		||||
            final AudioNode music = new AudioNode(app.getAssetManager(), name, DataType.Stream);
 | 
			
		||||
            music.setLooping(true);
 | 
			
		||||
            music.setVolume(volume);
 | 
			
		||||
            music.setPositional(false);
 | 
			
		||||
            music.setDirectional(false);
 | 
			
		||||
            return music;
 | 
			
		||||
        } catch (AssetLoadException | AssetNotFoundException ex) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, ex.getMessage(), ex);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays the current music if the music is enabled.
 | 
			
		||||
     * Stops the current music if the music is disabled.
 | 
			
		||||
     */
 | 
			
		||||
    private void playCurrentMusic() {
 | 
			
		||||
        if (isEnabled()) {
 | 
			
		||||
            if (currentMusic != null) {
 | 
			
		||||
                LOGGER.log(Level.INFO, "Playing current music"); //NON-NLS
 | 
			
		||||
                currentMusic.play();
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (currentMusic != null) {
 | 
			
		||||
                currentMusic.stop();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays the game music.
 | 
			
		||||
     */
 | 
			
		||||
    private void gameMusic() {
 | 
			
		||||
        if (isEnabled() && gameMusic != null) {
 | 
			
		||||
            stopAll();
 | 
			
		||||
            LOGGER.log(Level.INFO, "Playing game music"); //NON-NLS
 | 
			
		||||
            PREFERENCES.putFloat(VOLUME_PREF, volume);
 | 
			
		||||
            gameMusic.play();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays the victory music.
 | 
			
		||||
     */
 | 
			
		||||
    private void victoryMusic() {
 | 
			
		||||
        if (isEnabled() && victoryMusic != null) {
 | 
			
		||||
            stopAll();
 | 
			
		||||
            LOGGER.log(Level.INFO, "Playing victory music"); //NON-NLS
 | 
			
		||||
            PREFERENCES.putFloat(VOLUME_PREF, volume);
 | 
			
		||||
            victoryMusic.play();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays the defeat music.
 | 
			
		||||
     */
 | 
			
		||||
    private void defeatMusic() {
 | 
			
		||||
        if (isEnabled() && defeatMusic != null) {
 | 
			
		||||
            stopAll();
 | 
			
		||||
            LOGGER.log(Level.INFO, "Playing defeat music"); //NON-NLS
 | 
			
		||||
            PREFERENCES.putFloat(VOLUME_PREF, volume);
 | 
			
		||||
            defeatMusic.play();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stops all music.
 | 
			
		||||
     */
 | 
			
		||||
    private void stopAll() {
 | 
			
		||||
        if (menuMusic != null) menuMusic.stop();
 | 
			
		||||
        if (gameMusic != null) gameMusic.stop();
 | 
			
		||||
        if (victoryMusic != null) victoryMusic.stop();
 | 
			
		||||
        if (defeatMusic != null) defeatMusic.stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the received music event and plays the corresponding music.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event The music event to handle.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEvent(MusicEvent event) {
 | 
			
		||||
        switch (event.music()) {
 | 
			
		||||
            case GAME_MUSIC -> {
 | 
			
		||||
                gameMusic();
 | 
			
		||||
                currentMusic = gameMusic;
 | 
			
		||||
            }
 | 
			
		||||
            case VICTORY_MUSIC -> {
 | 
			
		||||
                victoryMusic();
 | 
			
		||||
                currentMusic = victoryMusic;
 | 
			
		||||
            }
 | 
			
		||||
            case DEFEAT_MUSIC -> {
 | 
			
		||||
                defeatMusic();
 | 
			
		||||
                currentMusic = defeatMusic;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the volume for the background music and updates the preferences.
 | 
			
		||||
     *
 | 
			
		||||
     * @param volume The volume level to set.
 | 
			
		||||
     */
 | 
			
		||||
    public void setVolume(float volume) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "Setting volume to {0}", volume); //NON-NLS
 | 
			
		||||
        this.volume = volume;
 | 
			
		||||
        currentMusic.setVolume(volume);
 | 
			
		||||
        PREFERENCES.putFloat(VOLUME_PREF, volume);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the volume level for the background music.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The volume level as a float.
 | 
			
		||||
     */
 | 
			
		||||
    public float getVolume() {
 | 
			
		||||
        return volume;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,442 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.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();
 | 
			
		||||
        attachBackgroundSound();
 | 
			
		||||
        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.
 | 
			
		||||
     * The background music state is responsible for managing the background music
 | 
			
		||||
     * playback in the game. It listens to the game logic for any changes in the
 | 
			
		||||
     * background music settings.
 | 
			
		||||
     */
 | 
			
		||||
    private void attachBackgroundSound() {
 | 
			
		||||
        final BackgroundMusic backgroundMusic = new BackgroundMusic();
 | 
			
		||||
        logic.addListener(backgroundMusic);
 | 
			
		||||
        backgroundMusic.setEnabled(BackgroundMusic.enabledInPreferences());
 | 
			
		||||
        stateManager.attach(backgroundMusic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,196 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,102 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,248 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.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.AnimationMessage;
 | 
			
		||||
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 {
 | 
			
		||||
    /**
 | 
			
		||||
     * Logger for the BattleshipServer class.
 | 
			
		||||
     */
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName());
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Configuration file for the server.
 | 
			
		||||
     */
 | 
			
		||||
    private static final File CONFIG_FILE = new File("server.properties");
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Port number for the server.
 | 
			
		||||
     */
 | 
			
		||||
    private final int PORT_NUMBER;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Configuration settings for the Battleship server.
 | 
			
		||||
     */
 | 
			
		||||
    private final BattleshipConfig config = new BattleshipConfig();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The server instance.
 | 
			
		||||
     */
 | 
			
		||||
    private Server myServer;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Game logic for the server.
 | 
			
		||||
     */
 | 
			
		||||
    private final ServerGameLogic logic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Queue for pending messages to be processed by the server.
 | 
			
		||||
     */
 | 
			
		||||
    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());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the server.
 | 
			
		||||
     */
 | 
			
		||||
    public BattleshipServer(int PORT_NUMBER) {
 | 
			
		||||
        config.readFromIfExists(CONFIG_FILE);
 | 
			
		||||
        this.PORT_NUMBER = PORT_NUMBER;
 | 
			
		||||
        LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
 | 
			
		||||
        logic = new ServerGameLogic(this, config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the server and processes incoming messages indefinitely.
 | 
			
		||||
     */
 | 
			
		||||
    public void run() {
 | 
			
		||||
        startServer();
 | 
			
		||||
        while (true)
 | 
			
		||||
            processNextMessage();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the server and initializes necessary components.
 | 
			
		||||
     * This method sets up the server, registers serializable classes,
 | 
			
		||||
     * starts the server, and registers listeners for incoming connections and messages.
 | 
			
		||||
     * If the server fails to start, it logs an error and exits the application.
 | 
			
		||||
     */
 | 
			
		||||
    private void startServer() {
 | 
			
		||||
        try {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
 | 
			
		||||
            myServer = Network.createServer(PORT_NUMBER);
 | 
			
		||||
            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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Processes the next message in the queue.
 | 
			
		||||
     * This method blocks until a message is available, then processes it using the server logic.
 | 
			
		||||
     * If interrupted while waiting, it logs the interruption and re-interrupts the thread.
 | 
			
		||||
     */
 | 
			
		||||
    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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers the serializable classes used by the server.
 | 
			
		||||
     * This method ensures that the necessary classes are registered with the serializer
 | 
			
		||||
     * so that they can be correctly transmitted over the network.
 | 
			
		||||
     */
 | 
			
		||||
    private void initializeSerializables() {
 | 
			
		||||
        Serializer.registerClass(GameDetails.class);
 | 
			
		||||
        Serializer.registerClass(StartBattleMessage.class);
 | 
			
		||||
        Serializer.registerClass(MapMessage.class);
 | 
			
		||||
        Serializer.registerClass(ShootMessage.class);
 | 
			
		||||
        Serializer.registerClass(AnimationMessage.class);
 | 
			
		||||
        Serializer.registerClass(EffectMessage.class);
 | 
			
		||||
        Serializer.registerClass(Battleship.class);
 | 
			
		||||
        Serializer.registerClass(IntPoint.class);
 | 
			
		||||
        Serializer.registerClass(Shot.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers listeners for incoming connections and messages.
 | 
			
		||||
     * This method adds message listeners for `MapMessage` and `ShootMessage` classes,
 | 
			
		||||
     * and a connection listener for handling connection events.
 | 
			
		||||
     */
 | 
			
		||||
    private void registerListeners() {
 | 
			
		||||
        myServer.addMessageListener(this, MapMessage.class);
 | 
			
		||||
        myServer.addMessageListener(this, ShootMessage.class);
 | 
			
		||||
        myServer.addMessageListener(this, AnimationMessage.class);
 | 
			
		||||
        myServer.addConnectionListener(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the reception of messages from clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source  the connection from which the message was received
 | 
			
		||||
     * @param message the message received from the client
 | 
			
		||||
     */
 | 
			
		||||
    @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()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when a new connection is added to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param server           the server to which the connection was added
 | 
			
		||||
     * @param hostedConnection the connection that was added
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void connectionAdded(Server server, HostedConnection hostedConnection) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
 | 
			
		||||
        logic.addPlayer(hostedConnection.getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when a connection is removed from the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param server           the server from which the connection was removed
 | 
			
		||||
     * @param hostedConnection the connection that was removed
 | 
			
		||||
     */
 | 
			
		||||
    @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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Exits the application with the specified exit value.
 | 
			
		||||
     * Closes all client connections and logs the close request.
 | 
			
		||||
     *
 | 
			
		||||
     * @param exitValue the exit value to be used when exiting the application
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,145 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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 shellFiredSound;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
        shellFiredSound = loadSound(app, "Sound/Effects/missle.wav"); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 sound effect when a shell has been fired.
 | 
			
		||||
     */
 | 
			
		||||
    public void shellFired() {
 | 
			
		||||
        if (isEnabled() && shellFiredSound != null)
 | 
			
		||||
            shellFiredSound.playInstance();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEvent(SoundEvent event) {
 | 
			
		||||
        switch (event.sound()) {
 | 
			
		||||
            case EXPLOSION -> explosion();
 | 
			
		||||
            case SPLASH -> splash();
 | 
			
		||||
            case DESTROYED_SHIP -> shipDestroyed();
 | 
			
		||||
            case SHELL_FIRED -> shellFired();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,182 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.simsilica.lemur.Button;
 | 
			
		||||
import com.simsilica.lemur.Checkbox;
 | 
			
		||||
import com.simsilica.lemur.DefaultRangedValueModel;
 | 
			
		||||
import com.simsilica.lemur.Label;
 | 
			
		||||
import com.simsilica.lemur.Slider;
 | 
			
		||||
import com.simsilica.lemur.core.VersionedReference;
 | 
			
		||||
import com.simsilica.lemur.style.ElementId;
 | 
			
		||||
import pp.dialog.Dialog;
 | 
			
		||||
import pp.dialog.StateCheckboxModel;
 | 
			
		||||
import pp.dialog.TextInputDialog;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.Resources.lookup;
 | 
			
		||||
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 Logger LOGGER = System.getLogger(Menu.class.getName());
 | 
			
		||||
    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 VersionedReference<Double> volumeRef;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs the Menu dialog for the Battleship application.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the BattleshipApp instance
 | 
			
		||||
     */
 | 
			
		||||
    public Menu(BattleshipApp app) {
 | 
			
		||||
        super(app.getDialogManager());
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        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.music-toggle"),
 | 
			
		||||
                new StateCheckboxModel(app, BackgroundMusic.class)));
 | 
			
		||||
 | 
			
		||||
        Slider volumeSlider = new Slider();
 | 
			
		||||
        volumeSlider.setModel(new DefaultRangedValueModel(0.0, 2.0, app.getStateManager().getState(BackgroundMusic.class).getVolume()));
 | 
			
		||||
        volumeSlider.setDelta(0.1f);
 | 
			
		||||
        addChild(volumeSlider);
 | 
			
		||||
        volumeRef = volumeSlider.getModel().createReference();
 | 
			
		||||
 | 
			
		||||
        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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the menu state based on the time per frame (tpf).
 | 
			
		||||
     * If the volume reference has been updated, adjusts the volume accordingly.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf the time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void update(float tpf) {
 | 
			
		||||
        if (volumeRef.update()) {
 | 
			
		||||
            adjustVolume(volumeRef.get());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adjusts the volume of the background music.
 | 
			
		||||
     *
 | 
			
		||||
     * @param volume the new volume level to set, as a double
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustVolume(double volume) {
 | 
			
		||||
        app.getStateManager().getState(BackgroundMusic.class).setVolume((float) volume);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,193 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.simsilica.lemur.Checkbox;
 | 
			
		||||
import com.simsilica.lemur.Container;
 | 
			
		||||
import com.simsilica.lemur.Label;
 | 
			
		||||
import com.simsilica.lemur.TextField;
 | 
			
		||||
import com.simsilica.lemur.component.SpringGridLayout;
 | 
			
		||||
import pp.dialog.Dialog;
 | 
			
		||||
import pp.dialog.DialogBuilder;
 | 
			
		||||
import pp.dialog.SimpleDialog;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Future;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.Resources.lookup;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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 = "1234"; //NON-NLS
 | 
			
		||||
    private static final int START_SERVER_DELAY = 2000;
 | 
			
		||||
    private final NetworkSupport network;
 | 
			
		||||
    private final TextField host = new TextField(LOCALHOST);
 | 
			
		||||
    private final TextField port = new TextField(DEFAULT_PORT);
 | 
			
		||||
    private String hostname;
 | 
			
		||||
    private int portNumber;
 | 
			
		||||
    private Future<Object> connectionFuture;
 | 
			
		||||
    private Dialog progressDialog;
 | 
			
		||||
    private boolean hostServer = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
 | 
			
		||||
        Checkbox hostCheckbox = new Checkbox(lookup("host.own-server"));
 | 
			
		||||
        hostCheckbox.setChecked(false);
 | 
			
		||||
        hostCheckbox.addClickCommands(s -> hostServer = !hostServer);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
        input.addChild(hostCheckbox);
 | 
			
		||||
 | 
			
		||||
        DialogBuilder.simple(app.getDialogManager())
 | 
			
		||||
                .setTitle(lookup("server.dialog"))
 | 
			
		||||
                .setExtension(d -> d.addChild(input))
 | 
			
		||||
                .setOkButton(lookup("button.connect"), d -> connectHostServer())
 | 
			
		||||
                .setNoButton(lookup("button.cancel"), app::closeApp)
 | 
			
		||||
                .setOkClose(false)
 | 
			
		||||
                .setNoClose(false)
 | 
			
		||||
                .build(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Connects to the host server. If the `hostServer` flag is set, it starts the server
 | 
			
		||||
     * before attempting to connect. If the server fails to start, logs an error.
 | 
			
		||||
     */
 | 
			
		||||
    private void connectHostServer() {
 | 
			
		||||
        if (hostServer) {
 | 
			
		||||
            startServer();
 | 
			
		||||
            try {
 | 
			
		||||
                Thread.sleep(START_SERVER_DELAY);
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                LOGGER.log(Level.ERROR, "Server start failed", e); //NON-NLS
 | 
			
		||||
            }
 | 
			
		||||
            connect();
 | 
			
		||||
        } else {
 | 
			
		||||
            connect();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the game server in a new thread.
 | 
			
		||||
     * Logs an error if the server fails to start.
 | 
			
		||||
     */
 | 
			
		||||
    private void startServer() {
 | 
			
		||||
        LOGGER.log(Level.INFO, "start server"); //NON-NLS
 | 
			
		||||
        new Thread(() -> {
 | 
			
		||||
            try {
 | 
			
		||||
                LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
 | 
			
		||||
                BattleshipServer server = new BattleshipServer(Integer.parseInt(port.getText()));
 | 
			
		||||
                LOGGER.log(Level.INFO, "Server started"); //NON-NLS
 | 
			
		||||
                server.run();
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                LOGGER.log(Level.ERROR, "Server start failed", e); //NON-NLS
 | 
			
		||||
            }
 | 
			
		||||
        }).start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,152 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ClientInterpreter;
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a received message from a client.
 | 
			
		||||
 *
 | 
			
		||||
 * @param message the client message
 | 
			
		||||
 * @param from    the ID of the sender
 | 
			
		||||
 */
 | 
			
		||||
record ReceivedMessage(ClientMessage message, int from) {
 | 
			
		||||
    /**
 | 
			
		||||
     * Processes the received message using the specified interpreter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param interpreter the client interpreter
 | 
			
		||||
     */
 | 
			
		||||
    void process(ClientInterpreter interpreter) {
 | 
			
		||||
        message.accept(interpreter, from);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,122 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,173 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,195 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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 {
 | 
			
		||||
    private 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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static float getFieldSize() {
 | 
			
		||||
        return FIELD_SIZE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,150 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.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 visual representation of a shell on the map.
 | 
			
		||||
     * The shell is represented as a black ellipse.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shell the Shell object representing the shell in the model
 | 
			
		||||
     * @return a Spatial representing the shell on the map
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shell shell) {
 | 
			
		||||
        Geometry ellipse = new Geometry("ellipse", new Sphere(50, 50, MapView.getFieldSize() / 2 * 0.8f));
 | 
			
		||||
        Material mat = new Material(view.getApp().getAssetManager(), UNSHADED); //NON-NLS
 | 
			
		||||
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
 | 
			
		||||
        mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
 | 
			
		||||
        mat.setColor("Color", ColorRGBA.Black);
 | 
			
		||||
        ellipse.setMaterial(mat);
 | 
			
		||||
        ellipse.addControl(new ShellMapControl(view, shell));
 | 
			
		||||
        return ellipse;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,203 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,371 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.effect.ParticleMesh;
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.material.RenderState.BlendMode;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.renderer.queue.RenderQueue;
 | 
			
		||||
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 pp.battleship.client.BattleshipApp;
 | 
			
		||||
import pp.battleship.model.*;
 | 
			
		||||
 | 
			
		||||
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 DESTROYER_MODEL = "Models/Destroyer/Destroyer.j3o"; //NON-NLS
 | 
			
		||||
    private static final String DESTROYER_TEXTURE = "Models/Destroyer/BattleshipC.jpg"; //NON-NLS
 | 
			
		||||
    private static final String TYPE_II_UBOAT_MODEL = "Models/TypeIIUboat/TypeIIUboat.j3o"; //NON-NLS
 | 
			
		||||
    private static final String TYPE_II_UBOAT_TEXTURE = "Models/TypeIIUboat/Type_II_U-boat_diff.jpg"; //NON-NLS
 | 
			
		||||
    private static final String ATLANTICA_MODEL = "Models/Atlantica/Atlantica.j3o"; //NON-NLS
 | 
			
		||||
    private static final String ROCKET = "Models/Rocket/Rocket.j3o"; //NON-NLS
 | 
			
		||||
 | 
			
		||||
    private static final String COLOR = "Color"; //NON-NLS
 | 
			
		||||
    private static final String SHIP = "ship"; //NON-NLS
 | 
			
		||||
    private static final String SHELL = "shell"; //NON-NLS
 | 
			
		||||
    private static final ColorRGBA BOX_COLOR = ColorRGBA.Gray;
 | 
			
		||||
 | 
			
		||||
    private final ShipMap map;
 | 
			
		||||
    private final BattleshipApp app;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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;
 | 
			
		||||
        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 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");
 | 
			
		||||
 | 
			
		||||
        final ParticleEmitter debris = createDebrisEffect(shot);
 | 
			
		||||
        shipNode.attachChild(debris);
 | 
			
		||||
 | 
			
		||||
        final ParticleEmitter fire = createFireEffect(shot, shipNode);
 | 
			
		||||
        shipNode.attachChild(fire);
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Spatial handleMiss(Shot shot) {
 | 
			
		||||
        return createMissEffect(shot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ParticleEmitter createMissEffect(Shot shot) {
 | 
			
		||||
        final ParticleEmitter water = new ParticleEmitter("WaterEmitter", ParticleMesh.Type.Triangle, 20);
 | 
			
		||||
 | 
			
		||||
        Material waterMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        waterMaterial.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png"));
 | 
			
		||||
        water.setMaterial(waterMaterial);
 | 
			
		||||
 | 
			
		||||
        water.setImagesX(2);
 | 
			
		||||
        water.setImagesY(2);
 | 
			
		||||
        water.setStartColor(ColorRGBA.Cyan);
 | 
			
		||||
        water.setEndColor(ColorRGBA.Blue);
 | 
			
		||||
        water.getParticleInfluencer().setInitialVelocity(new Vector3f(0.1f, 0.1f, 0.1f));
 | 
			
		||||
        water.setStartSize(0.4f);
 | 
			
		||||
        water.setEndSize(0.45f);
 | 
			
		||||
        water.setGravity(0, -0.5f, 0);
 | 
			
		||||
        water.setLowLife(1f);
 | 
			
		||||
        water.setHighLife(1f);
 | 
			
		||||
        water.setParticlesPerSec(0);
 | 
			
		||||
 | 
			
		||||
        water.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
 | 
			
		||||
        water.emitAllParticles();
 | 
			
		||||
        return water;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ParticleEmitter createDebrisEffect(Shot shot) {
 | 
			
		||||
        final ParticleEmitter debris = new ParticleEmitter("DebrisEmitter", ParticleMesh.Type.Triangle, 2);
 | 
			
		||||
 | 
			
		||||
        Material debrisMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        debrisMaterial.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/Debris.png"));
 | 
			
		||||
        debris.setMaterial(debrisMaterial);
 | 
			
		||||
 | 
			
		||||
        debris.setImagesX(2);
 | 
			
		||||
        debris.setImagesY(2);
 | 
			
		||||
        debris.setStartColor(ColorRGBA.White);
 | 
			
		||||
        debris.setEndColor(ColorRGBA.White);
 | 
			
		||||
        debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0.1f, 2f, 0.1f));
 | 
			
		||||
        debris.setStartSize(0.1f);
 | 
			
		||||
        debris.setEndSize(0.5f);
 | 
			
		||||
        debris.setGravity(0, 3f, 0);
 | 
			
		||||
        debris.getParticleInfluencer().setVelocityVariation(.40f);
 | 
			
		||||
        debris.setLowLife(1f);
 | 
			
		||||
        debris.setHighLife(1.5f);
 | 
			
		||||
        debris.setParticlesPerSec(0);
 | 
			
		||||
 | 
			
		||||
        debris.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
 | 
			
		||||
        debris.emitAllParticles();
 | 
			
		||||
        return debris;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ParticleEmitter createFireEffect(Shot shot, Node shipNode) {
 | 
			
		||||
        ParticleEmitter fire = new ParticleEmitter("FireEmitter", ParticleMesh.Type.Triangle, 100);
 | 
			
		||||
 | 
			
		||||
        Material fireMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
        fireMaterial.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png"));
 | 
			
		||||
        fire.setMaterial(fireMaterial);
 | 
			
		||||
 | 
			
		||||
        fire.setImagesX(2);
 | 
			
		||||
        fire.setImagesY(2);
 | 
			
		||||
        fire.setStartColor(ColorRGBA.Orange);
 | 
			
		||||
        fire.setEndColor(ColorRGBA.Red);
 | 
			
		||||
        fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1.5f, 0));
 | 
			
		||||
        fire.setStartSize(0.2f);
 | 
			
		||||
        fire.setEndSize(0.05f);
 | 
			
		||||
        fire.setLowLife(1f);
 | 
			
		||||
        fire.setHighLife(2f);
 | 
			
		||||
        fire.getParticleInfluencer().setVelocityVariation(0.2f);
 | 
			
		||||
 | 
			
		||||
        fire.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
 | 
			
		||||
        fire.getLocalTranslation().subtractLocal(shipNode.getLocalTranslation());
 | 
			
		||||
 | 
			
		||||
        return fire;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Visits a {@link Shell} and creates a graphical representation of it.
 | 
			
		||||
     * The shell is represented as a node with a model attached to it.
 | 
			
		||||
     * The node is then positioned and controlled by a {@link ShellControl}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shell the shell to be represented
 | 
			
		||||
     * @return the node containing the graphical representation of the shell
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shell shell) {
 | 
			
		||||
        final Node node = new Node(SHELL);
 | 
			
		||||
        node.attachChild(createShell());
 | 
			
		||||
        node.setLocalTranslation(shell.getY() + 0.5f, 10f, shell.getX() + 0.5f);
 | 
			
		||||
        node.addControl(new ShellControl());
 | 
			
		||||
        return node;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a graphical representation of a shell.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the spatial representing the shell
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createShell() {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(ROCKET);
 | 
			
		||||
        model.scale(0.0025f);
 | 
			
		||||
        model.rotate(PI, 0f, 0f);
 | 
			
		||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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) {
 | 
			
		||||
        return switch (ship.getLength()) {
 | 
			
		||||
            case 1 -> createVessel(ship);
 | 
			
		||||
            case 2 -> createSubmarine(ship);
 | 
			
		||||
            case 3 -> createDestroyer(ship);
 | 
			
		||||
            case 4 -> createBattleship(ship);
 | 
			
		||||
            default -> 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());
 | 
			
		||||
        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.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a {@link Material} instance configured with the specified color and,
 | 
			
		||||
     * if necessary, alpha blending enabled.
 | 
			
		||||
     */
 | 
			
		||||
    private Material createColoredMaterial() {
 | 
			
		||||
        final Material material = new Material(app.getAssetManager(), UNSHADED);
 | 
			
		||||
        if (SeaSynchronizer.BOX_COLOR.getAlpha() < 1f)
 | 
			
		||||
            material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
 | 
			
		||||
        material.setColor(COLOR, SeaSynchronizer.BOX_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.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a detailed 3D model to represent a destroyer battleship.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to be represented
 | 
			
		||||
     * @return the spatial representing the destroyer battleship
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createDestroyer(Battleship ship) {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(DESTROYER_MODEL);
 | 
			
		||||
 | 
			
		||||
        Material mat = new Material(app.getAssetManager(), UNSHADED);
 | 
			
		||||
        mat.setTexture("ColorMap", app.getAssetManager().loadTexture(DESTROYER_TEXTURE));
 | 
			
		||||
        mat.getAdditionalRenderState().setBlendMode(BlendMode.Off);
 | 
			
		||||
        model.setMaterial(mat);
 | 
			
		||||
 | 
			
		||||
        model.setQueueBucket(RenderQueue.Bucket.Opaque);
 | 
			
		||||
 | 
			
		||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
 | 
			
		||||
        model.scale(0.1f);
 | 
			
		||||
        model.setLocalTranslation(0f, 0.25f, 0f);
 | 
			
		||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a detailed 3D model to represent a Type II U-boat submarine.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to be represented
 | 
			
		||||
     * @return the spatial representing the Type II U-boat submarine
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createSubmarine(Battleship ship) {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(TYPE_II_UBOAT_MODEL);
 | 
			
		||||
 | 
			
		||||
        Material mat = new Material(app.getAssetManager(), UNSHADED);
 | 
			
		||||
        mat.setTexture("ColorMap", app.getAssetManager().loadTexture(TYPE_II_UBOAT_TEXTURE));
 | 
			
		||||
        model.setMaterial(mat);
 | 
			
		||||
 | 
			
		||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
 | 
			
		||||
        model.scale(0.25f);
 | 
			
		||||
        model.getLocalTranslation().addLocal(0f, -0.15f, 0f);
 | 
			
		||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a detailed 3D model to represent a vessel.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to be represented
 | 
			
		||||
     * @return the spatial representing the vessel
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createVessel(Battleship ship) {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(ATLANTICA_MODEL);
 | 
			
		||||
 | 
			
		||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
 | 
			
		||||
        model.scale(0.0003f);
 | 
			
		||||
        model.getLocalTranslation().addLocal(0f, -0.05f, 0f);
 | 
			
		||||
        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;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.renderer.RenderManager;
 | 
			
		||||
import com.jme3.renderer.ViewPort;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls the movement and rotation of a shell in the game.
 | 
			
		||||
 * The shell moves downward at a constant speed and rotates around its Y-axis.
 | 
			
		||||
 * When the shell reaches a certain Y-coordinate, it is removed from its parent node.
 | 
			
		||||
 */
 | 
			
		||||
public class ShellControl extends AbstractControl {
 | 
			
		||||
    private final static float SHELL_SPEED = 7.5f;
 | 
			
		||||
    private final static float SHELL_ROTATION_SPEED = 0.5f;
 | 
			
		||||
    private final static float MIN_HEIGHT = 0.7f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the shell's position and rotation.
 | 
			
		||||
     * If the shell's Y-coordinate is less than or equal to 1.0, it is detached from its parent node.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame, used to ensure consistent movement speed across different frame rates
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        spatial.move(0, -SHELL_SPEED * tpf, 0);
 | 
			
		||||
        spatial.rotate(0, SHELL_ROTATION_SPEED, 0);
 | 
			
		||||
        if (spatial.getLocalTranslation().getY() <= MIN_HEIGHT) {
 | 
			
		||||
            spatial.getParent().detachChild(spatial);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Renders the shell. This method is currently not used.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rm the RenderManager
 | 
			
		||||
     * @param vp the ViewPort
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlRender(RenderManager rm, ViewPort vp) {
 | 
			
		||||
        // nothing to do here
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,82 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls the animation of a shell in the map view.
 | 
			
		||||
 * This class handles the movement of a shell from its starting position to its target position
 | 
			
		||||
 * using linear interpolation over a specified duration.
 | 
			
		||||
 */
 | 
			
		||||
public class ShellMapControl extends AbstractControl {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(ShellMapControl.class.getName());
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The duration of the shell animation in seconds.
 | 
			
		||||
     */
 | 
			
		||||
    private final static float ANIMATION_DURATION = 0.8f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The end position of the shell in the map view.
 | 
			
		||||
     */
 | 
			
		||||
    private final Position endPos;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The progress of the shell's movement, ranging from 0 to 1.
 | 
			
		||||
     */
 | 
			
		||||
    private float progress = 0f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new instance of {@link ShellMapControl}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param view  the map view
 | 
			
		||||
     * @param shell the shell to be controlled
 | 
			
		||||
     */
 | 
			
		||||
    public ShellMapControl(MapView view, Shell shell) {
 | 
			
		||||
        Vector3f endPos = new Vector3f(shell.getX(), 0, shell.getY());
 | 
			
		||||
        this.endPos = view.modelToView(endPos.x, endPos.z);
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "ShellMapControl created with endPos: " + this.endPos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the position of the shell in the view with linear interpolation.
 | 
			
		||||
     * This method is called during the update phase.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf the time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        // adjust speed by changing the multiplier
 | 
			
		||||
        progress += tpf * ANIMATION_DURATION;
 | 
			
		||||
 | 
			
		||||
        // progress is between 0 and 1
 | 
			
		||||
        if (progress > 1f) {
 | 
			
		||||
            progress = 1f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // linearly interpolate the current position between (0, 0) and endPos
 | 
			
		||||
        float newX = (1 - progress) * 0 + progress * endPos.getX() + MapView.getFieldSize() / 2;
 | 
			
		||||
        float newZ = (1 - progress) * 0 + progress * endPos.getY() + MapView.getFieldSize() / 2;
 | 
			
		||||
 | 
			
		||||
        spatial.setLocalTranslation(newX, newZ, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This method is called during the render phase.
 | 
			
		||||
     * Currently, it does nothing.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rm the RenderManager
 | 
			
		||||
     * @param vp the ViewPort
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlRender(RenderManager rm, ViewPort vp) {
 | 
			
		||||
        // nothing to do here
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,121 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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;
 | 
			
		||||
 | 
			
		||||
    private final Battleship ship;
 | 
			
		||||
 | 
			
		||||
    private static final float SINKING_HEIGHT = -0.6f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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();
 | 
			
		||||
 | 
			
		||||
        this.ship = ship;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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;
 | 
			
		||||
 | 
			
		||||
        // Handle ship sinking by moving it downwards
 | 
			
		||||
        if (ship.isDestroyed()) {
 | 
			
		||||
            if (spatial.getLocalTranslation().getY() < SINKING_HEIGHT) {
 | 
			
		||||
                spatial.getParent().detachChild(spatial);
 | 
			
		||||
            } else {
 | 
			
		||||
                spatial.move(0, -tpf * 0.1f, 0);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,89 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,104 +0,0 @@
 | 
			
		||||
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
 | 
			
		||||
# File Created: 16.12.2011 14:18:52
 | 
			
		||||
 | 
			
		||||
newmtl white
 | 
			
		||||
	Ns 53.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.6667 0.6667 0.6667
 | 
			
		||||
	Kd 0.6667 0.6667 0.6667
 | 
			
		||||
	Ks 0.1800 0.1800 0.1800
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl boat_elements_black
 | 
			
		||||
	Ns 55.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.0000 0.0000 0.0000
 | 
			
		||||
	Kd 0.0000 0.0000 0.0000
 | 
			
		||||
	Ks 0.3600 0.3600 0.3600
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl boat_glass
 | 
			
		||||
	Ns 60.0000
 | 
			
		||||
	Ni 7.0000
 | 
			
		||||
	d 0.4000
 | 
			
		||||
	Tr 0.6000
 | 
			
		||||
	Tf 0.4000 0.4000 0.4000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.1059 0.1569 0.1451
 | 
			
		||||
	Kd 0.1059 0.1569 0.1451
 | 
			
		||||
	Ks 0.6750 0.6750 0.6750
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl boat_screw_hooks_bronze
 | 
			
		||||
	Ns 80.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.2941 0.2157 0.0510
 | 
			
		||||
	Kd 0.2941 0.2157 0.0510
 | 
			
		||||
	Ks 0.7200 0.7200 0.7200
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl boat_silver
 | 
			
		||||
	Ns 80.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.3333 0.3333 0.3333
 | 
			
		||||
	Kd 0.3333 0.3333 0.3333
 | 
			
		||||
	Ks 0.7200 0.7200 0.7200
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl boat_buffer
 | 
			
		||||
	Ns 10.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 1.0000 1.0000 1.0000
 | 
			
		||||
	Kd 1.0000 1.0000 1.0000
 | 
			
		||||
	Ks 0.2700 0.2700 0.2700
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
	map_Ka boat_buffer_diffuse.jpg
 | 
			
		||||
	map_Kd boat_buffer_diffuse.jpg
 | 
			
		||||
 | 
			
		||||
newmtl boat_roof_accessory
 | 
			
		||||
	Ns 15.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 1.0000 1.0000 1.0000
 | 
			
		||||
	Kd 1.0000 1.0000 1.0000
 | 
			
		||||
	Ks 0.3600 0.3600 0.3600
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
	map_Ka boat_roof_accessory_diffuse.jpg
 | 
			
		||||
	map_Kd boat_roof_accessory_diffuse.jpg
 | 
			
		||||
 | 
			
		||||
newmtl boat_body
 | 
			
		||||
	Ns 55.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 1.0000 1.0000 1.0000
 | 
			
		||||
	Kd 1.0000 1.0000 1.0000
 | 
			
		||||
	Ks 0.3600 0.3600 0.3600
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
	map_Ka boat_body_diffuse.jpg
 | 
			
		||||
	map_Kd boat_body_diffuse.jpg
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/boat-v2--225787.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 56 KiB  | 
| 
		 Before Width: | Height: | Size: 166 KiB  | 
| 
		 Before Width: | Height: | Size: 98 KiB  | 
@@ -1,92 +0,0 @@
 | 
			
		||||
# Blender 4.1.0 MTL File: 'None'
 | 
			
		||||
# www.blender.org
 | 
			
		||||
 | 
			
		||||
newmtl Battleship
 | 
			
		||||
Ns 256.000031
 | 
			
		||||
Ka 1.000000 1.000000 1.000000
 | 
			
		||||
Ks 0.000000 0.000000 0.000000
 | 
			
		||||
Ke 0.000000 0.000000 0.000000
 | 
			
		||||
Ni 1.000000
 | 
			
		||||
d 1.000000
 | 
			
		||||
illum 1
 | 
			
		||||
map_Kd BattleshipC.jpg
 | 
			
		||||
 | 
			
		||||
newmtl blinn1SG
 | 
			
		||||
Ns 256.000031
 | 
			
		||||
Ka 1.000000 1.000000 1.000000
 | 
			
		||||
Kd 0.500000 0.500000 0.500000
 | 
			
		||||
Ks 0.000000 0.000000 0.000000
 | 
			
		||||
Ke 0.000000 0.000000 0.000000
 | 
			
		||||
Ni 1.000000
 | 
			
		||||
d 1.000000
 | 
			
		||||
illum 1
 | 
			
		||||
 | 
			
		||||
newmtl blinn2SG
 | 
			
		||||
Ns 256.000031
 | 
			
		||||
Ka 1.000000 1.000000 1.000000
 | 
			
		||||
Kd 0.500000 0.500000 0.500000
 | 
			
		||||
Ks 0.000000 0.000000 0.000000
 | 
			
		||||
Ke 0.000000 0.000000 0.000000
 | 
			
		||||
Ni 1.000000
 | 
			
		||||
d 1.000000
 | 
			
		||||
illum 1
 | 
			
		||||
 | 
			
		||||
newmtl blinn3SG
 | 
			
		||||
Ns 256.000031
 | 
			
		||||
Ka 1.000000 1.000000 1.000000
 | 
			
		||||
Kd 0.500000 0.500000 0.500000
 | 
			
		||||
Ks 0.500000 0.500000 0.500000
 | 
			
		||||
Ke 0.000000 0.000000 0.000000
 | 
			
		||||
Ni 1.000000
 | 
			
		||||
d 1.000000
 | 
			
		||||
illum 2
 | 
			
		||||
 | 
			
		||||
newmtl blinn4SG
 | 
			
		||||
Ns 256.000031
 | 
			
		||||
Ka 1.000000 1.000000 1.000000
 | 
			
		||||
Kd 0.500000 0.500000 0.500000
 | 
			
		||||
Ks 0.500000 0.500000 0.500000
 | 
			
		||||
Ke 0.000000 0.000000 0.000000
 | 
			
		||||
Ni 1.000000
 | 
			
		||||
d 1.000000
 | 
			
		||||
illum 2
 | 
			
		||||
 | 
			
		||||
newmtl blinn5SG
 | 
			
		||||
Ns 256.000031
 | 
			
		||||
Ka 1.000000 1.000000 1.000000
 | 
			
		||||
Kd 0.500000 0.500000 0.500000
 | 
			
		||||
Ks 0.500000 0.500000 0.500000
 | 
			
		||||
Ke 0.000000 0.000000 0.000000
 | 
			
		||||
Ni 1.000000
 | 
			
		||||
d 1.000000
 | 
			
		||||
illum 2
 | 
			
		||||
 | 
			
		||||
newmtl blinn6SG
 | 
			
		||||
Ns 256.000031
 | 
			
		||||
Ka 1.000000 1.000000 1.000000
 | 
			
		||||
Kd 0.500000 0.500000 0.500000
 | 
			
		||||
Ks 0.500000 0.500000 0.500000
 | 
			
		||||
Ke 0.000000 0.000000 0.000000
 | 
			
		||||
Ni 1.000000
 | 
			
		||||
d 1.000000
 | 
			
		||||
illum 2
 | 
			
		||||
 | 
			
		||||
newmtl blinn7SG
 | 
			
		||||
Ns 256.000031
 | 
			
		||||
Ka 1.000000 1.000000 1.000000
 | 
			
		||||
Kd 0.500000 0.500000 0.500000
 | 
			
		||||
Ks 0.500000 0.500000 0.500000
 | 
			
		||||
Ke 0.000000 0.000000 0.000000
 | 
			
		||||
Ni 1.000000
 | 
			
		||||
d 1.000000
 | 
			
		||||
illum 2
 | 
			
		||||
 | 
			
		||||
newmtl blinn8SG
 | 
			
		||||
Ns 256.000031
 | 
			
		||||
Ka 1.000000 1.000000 1.000000
 | 
			
		||||
Kd 0.500000 0.500000 0.500000
 | 
			
		||||
Ks 0.500000 0.500000 0.500000
 | 
			
		||||
Ke 0.000000 0.000000 0.000000
 | 
			
		||||
Ni 1.000000
 | 
			
		||||
d 1.000000
 | 
			
		||||
illum 2
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 360 KiB  | 
@@ -1,3 +0,0 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/battleship-v1--611736.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 235 KiB  | 
| 
		 Before Width: | Height: | Size: 94 KiB  | 
@@ -1,3 +0,0 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/wwii-ship-uk-king-george-v-class-battleship-v1--185381.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
@@ -1,250 +0,0 @@
 | 
			
		||||
# 
 | 
			
		||||
# Generated by Sweet Home 3D - ven. janv. 02 20:37:08 CET 2015
 | 
			
		||||
# http://www.sweethome3d.com/
 | 
			
		||||
# 
 | 
			
		||||
 | 
			
		||||
newmtl FrontColorNoCulling
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.2 0.2 0.2
 | 
			
		||||
Kd 0.2 0.2 0.2
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 0.0
 | 
			
		||||
 | 
			
		||||
newmtl ForegroundColor
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.2 0.2 0.2
 | 
			
		||||
Kd 0.2 0.2 0.2
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 0.0
 | 
			
		||||
 | 
			
		||||
newmtl white
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.48235294 0.5019608 0.5803922
 | 
			
		||||
Kd 0.48235294 0.5019608 0.5803922
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 0.0
 | 
			
		||||
 | 
			
		||||
newmtl white_Cylinder_5
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.47843137 0.49803922 0.5764706
 | 
			
		||||
Kd 0.47843137 0.49803922 0.5764706
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 0.0
 | 
			
		||||
 | 
			
		||||
newmtl white_Cylinder_10
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.8784314 0.8745098 0.8901961
 | 
			
		||||
Kd 0.8784314 0.8745098 0.8901961
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 0.0
 | 
			
		||||
 | 
			
		||||
newmtl FrontColorNoCulling_11
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.8784314 0.8745098 0.8901961
 | 
			
		||||
Kd 0.8784314 0.8745098 0.8901961
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 0.0
 | 
			
		||||
 | 
			
		||||
newmtl ForegroundColor_12
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.8784314 0.8745098 0.8901961
 | 
			
		||||
Kd 0.8784314 0.8745098 0.8901961
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 0.0
 | 
			
		||||
 | 
			
		||||
newmtl white_Mesh_13
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.6 0.6 0.6
 | 
			
		||||
Kd 0.6 0.6 0.6
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 0.0
 | 
			
		||||
 | 
			
		||||
newmtl Cube_1_1_1
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.0 0.0 0.0
 | 
			
		||||
Kd 0.0 0.0 0.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_7_7
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.4 0.4 0.4
 | 
			
		||||
Kd 0.4 0.4 0.4
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_10_10
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.8 0.4 0.0
 | 
			
		||||
Kd 0.8 0.4 0.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_11_11
 | 
			
		||||
illum 2
 | 
			
		||||
Ka 0.2 0.2 0.2
 | 
			
		||||
Kd 0.2 0.2 0.2
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl 12_12
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.2 0.2 0.2
 | 
			
		||||
Kd 0.2 0.2 0.2
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cube_1_1_1_Cube_1_1_1_38
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.6 0.6 0.6
 | 
			
		||||
Kd 0.6 0.6 0.6
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl white_Cylinder_58
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.1882353 0.27058825 0.58431375
 | 
			
		||||
Kd 0.1882353 0.27058825 0.58431375
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 0.0
 | 
			
		||||
 | 
			
		||||
newmtl white_Cylinder_59
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.3137255 0.14901961 0.011764706
 | 
			
		||||
Kd 0.3137255 0.14901961 0.011764706
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 0.0
 | 
			
		||||
 | 
			
		||||
newmtl 1_1
 | 
			
		||||
illum 2
 | 
			
		||||
Ka 0.2 0.2 0.2
 | 
			
		||||
Kd 1.0 1.0 1.0
 | 
			
		||||
Ks 0.5 0.5 0.5
 | 
			
		||||
Ns 64.0
 | 
			
		||||
Ni 1.0
 | 
			
		||||
d 0.48000002
 | 
			
		||||
map_Kd Missile_AIM-120_D_[AMRAAM]_1_1.png
 | 
			
		||||
 | 
			
		||||
newmtl Cube_1_2_2
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.8 0.4 0.0
 | 
			
		||||
Kd 0.8 0.4 0.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_4_4
 | 
			
		||||
illum 2
 | 
			
		||||
Ka 0.6 0.6 0.6
 | 
			
		||||
Kd 0.6 0.6 0.6
 | 
			
		||||
Ks 0.5 0.5 0.5
 | 
			
		||||
Ns 64.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_5_5
 | 
			
		||||
illum 2
 | 
			
		||||
Ka 0.8 0.8 0.0
 | 
			
		||||
Kd 0.8 0.8 0.0
 | 
			
		||||
Ks 0.5 0.5 0.5
 | 
			
		||||
Ns 64.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_6_6
 | 
			
		||||
illum 2
 | 
			
		||||
Ka 0.8784314 0.8745098 0.8901961
 | 
			
		||||
Kd 0.8784314 0.8745098 0.8901961
 | 
			
		||||
Ks 0.5 0.5 0.5
 | 
			
		||||
Ns 64.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_10_10_Cylinder_10_10_73
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.2 0.2 0.2
 | 
			
		||||
Kd 0.2 0.2 0.2
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl 11_11
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.6 0.6 0.6
 | 
			
		||||
Kd 0.6 0.6 0.6
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cube_1_1_1_Cube_1_1_1_76
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.2 0.2 0.2
 | 
			
		||||
Kd 1.0 1.0 1.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
Ni 1.0
 | 
			
		||||
map_Kd Missile_AIM-120_D_[AMRAAM]_Cube_1_1_1_Cube_1_1_1_76.png
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_2_2
 | 
			
		||||
illum 2
 | 
			
		||||
Ka 0.6 0.6 0.6
 | 
			
		||||
Kd 0.6 0.6 0.6
 | 
			
		||||
Ks 0.5 0.5 0.5
 | 
			
		||||
Ns 64.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_3_3
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.4 0.4 0.0
 | 
			
		||||
Kd 0.4 0.4 0.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_4_4_Cylinder_4_4_79
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.0 0.0 0.0
 | 
			
		||||
Kd 0.0 0.0 0.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cube_1_5_5
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.2 0.2 0.2
 | 
			
		||||
Kd 1.0 1.0 1.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
map_Kd Missile_AIM-120_D_[AMRAAM]_Cube_1_5_5.png
 | 
			
		||||
 | 
			
		||||
newmtl Cube_1_6_6
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.2 0.2 0.2
 | 
			
		||||
Kd 1.0 1.0 1.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
Ni 1.0
 | 
			
		||||
map_Kd Missile_AIM-120_D_[AMRAAM]_Cube_1_6_6.png
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_1_1
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.4 0.4 0.4
 | 
			
		||||
Kd 0.4 0.4 0.4
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cube_1_5_5_Cube_1_5_5_86
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.2 0.2 0.2
 | 
			
		||||
Kd 0.2 0.2 0.2
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cube_1_6_6_Cube_1_6_6_87
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.8 0.0 0.0
 | 
			
		||||
Kd 0.8 0.0 0.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_7_7_Cylinder_7_7_88
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.8 0.4 0.0
 | 
			
		||||
Kd 0.8 0.4 0.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
 | 
			
		||||
newmtl Cylinder_8_8
 | 
			
		||||
illum 1
 | 
			
		||||
Ka 0.4 0.6 0.0
 | 
			
		||||
Kd 0.4 0.6 0.0
 | 
			
		||||
Ks 0.0 0.0 0.0
 | 
			
		||||
Ns 1.0
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 210 KiB  | 
| 
		 Before Width: | Height: | Size: 164 KiB  | 
| 
		 Before Width: | Height: | Size: 289 KiB  | 
| 
		 Before Width: | Height: | Size: 26 KiB  | 
@@ -1,3 +0,0 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/de/3d-model/aim-120d-missile-51025.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/wwii-ship-german-type-ii-uboat-v2--700733.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 168 KiB  | 
@@ -1,16 +0,0 @@
 | 
			
		||||
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
 | 
			
		||||
# File Created: 29.03.2012 14:25:39
 | 
			
		||||
 | 
			
		||||
newmtl default
 | 
			
		||||
	Ns 35.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 1.0000 1.0000 1.0000
 | 
			
		||||
	Kd 1.0000 1.0000 1.0000
 | 
			
		||||
	Ks 0.5400 0.5400 0.5400
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
	map_Ka 14084_WWII_ship_German_Type_II_U-boat_diff.jpg
 | 
			
		||||
	map_Kd 14084_WWII_ship_German_Type_II_U-boat_diff.jpg
 | 
			
		||||
@@ -1,10 +0,0 @@
 | 
			
		||||
Personal-use only.
 | 
			
		||||
 | 
			
		||||
menu_music.ogg
 | 
			
		||||
https://pixabay.com/de/music/szenen-aufbauen-demolition-outline-science-fiction-trailer-music-191960/
 | 
			
		||||
pirates.ogg
 | 
			
		||||
https://pixabay.com/de/music/epische-klassik-pirates-163389/
 | 
			
		||||
win_the_game.gg
 | 
			
		||||
https://pixabay.com/de/users/enrico_dering-31760131/
 | 
			
		||||
defeat.ogg
 | 
			
		||||
https://pixabay.com/de/music/dramaszene-defeat-charles-michel-140604/
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
based on:
 | 
			
		||||
water 002
 | 
			
		||||
by Katsukagi on 29/11/2018
 | 
			
		||||
https://3dtextures.me/2018/11/29/water-002/
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
Material Water : Common/MatDefs/Light/Lighting.j3md {
 | 
			
		||||
     MaterialParameters {
 | 
			
		||||
         Shininess            : 64
 | 
			
		||||
         DiffuseMap           : Repeat Textures/Terrain/Water/Water_002_COLOR.jpg
 | 
			
		||||
         NormalMap            : Repeat Textures/Terrain/Water/Water_002_NORM.jpg
 | 
			
		||||
         SpecularMap          : Repeat Textures/Terrain/Water/Water_002_ROUGH.jpg
 | 
			
		||||
         ParallaxMap          : Repeat Textures/Terrain/Water/Water_002_DISP.png
 | 
			
		||||
         // PackedNormalParallax : true
 | 
			
		||||
         // UseMaterialColors : true
 | 
			
		||||
         Ambient  : 0.5 0.5 0.5 1.0
 | 
			
		||||
         Diffuse  : 1.0 1.0 1.0 1.0
 | 
			
		||||
         Specular : 1.0 1.0 1.0 1.0
 | 
			
		||||
     }
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 15 KiB  | 
| 
		 Before Width: | Height: | Size: 207 KiB  | 
| 
		 Before Width: | Height: | Size: 88 KiB  | 
| 
		 Before Width: | Height: | Size: 31 KiB  | 
| 
		 Before Width: | Height: | Size: 70 KiB  | 
@@ -1,16 +0,0 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'buildlogic.jme-application-conventions'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
description = 'Battleship converter for resources'
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation libs.jme3.core
 | 
			
		||||
 | 
			
		||||
    runtimeOnly libs.jme3.desktop
 | 
			
		||||
    runtimeOnly libs.jme3.plugins
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
application {
 | 
			
		||||
    mainClass = 'pp.battleship.exporter.ModelExporter'
 | 
			
		||||
}
 | 
			
		||||
@@ -1,69 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.exporter;
 | 
			
		||||
 | 
			
		||||
import com.jme3.app.SimpleApplication;
 | 
			
		||||
import com.jme3.export.JmeExporter;
 | 
			
		||||
import com.jme3.export.binary.BinaryExporter;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import com.jme3.system.JmeContext;
 | 
			
		||||
import com.jme3.util.TangentBinormalGenerator;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class transforms models into j3o format.
 | 
			
		||||
 */
 | 
			
		||||
public class ModelExporter extends SimpleApplication {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(ModelExporter.class.getName());
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The main method of the converter
 | 
			
		||||
     *
 | 
			
		||||
     * @param args input args
 | 
			
		||||
     */
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        ModelExporter application = new ModelExporter();
 | 
			
		||||
        application.start(JmeContext.Type.Headless);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Overrides  {@link com.jme3.app.SimpleApplication#simpleInitApp()}.
 | 
			
		||||
     * It initializes a simple app by exporting robots and rocks.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void simpleInitApp() {
 | 
			
		||||
        export("Models/KingGeorgeV/King_George_V.obj", "KingGeorgeV.j3o"); //NON-NLS
 | 
			
		||||
 | 
			
		||||
        stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Exports spatial into a file
 | 
			
		||||
     *
 | 
			
		||||
     * @param fileName       the file name of the model
 | 
			
		||||
     * @param exportFileName the name of the file where the .j3o file is going to be stored
 | 
			
		||||
     */
 | 
			
		||||
    private void export(String fileName, String exportFileName) {
 | 
			
		||||
        final File file = new File(exportFileName);
 | 
			
		||||
        JmeExporter exporter = BinaryExporter.getInstance();
 | 
			
		||||
        try {
 | 
			
		||||
            final Spatial model = getAssetManager().loadModel(fileName);
 | 
			
		||||
            TangentBinormalGenerator.generate(model);
 | 
			
		||||
            exporter.save(model, file);
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException exception) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "write to {0} failed", file); //NON-NLS
 | 
			
		||||
            throw new RuntimeException(exception);
 | 
			
		||||
        }
 | 
			
		||||
        LOGGER.log(Level.INFO, "wrote file {0}", file); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 235 KiB  | 
@@ -1,18 +0,0 @@
 | 
			
		||||
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
 | 
			
		||||
# File Created: 16.03.2012 14:15:53
 | 
			
		||||
 | 
			
		||||
newmtl _King_George_V
 | 
			
		||||
	Ns 60.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 1.0000 1.0000 1.0000
 | 
			
		||||
	Kd 1.0000 1.0000 1.0000
 | 
			
		||||
	Ks 0.4500 0.4500 0.4500
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
	map_Ka King_George_V.jpg
 | 
			
		||||
	map_Kd King_George_V.jpg
 | 
			
		||||
	map_bump King_George_V_bump.jpg
 | 
			
		||||
	bump King_George_V_bump.jpg
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 94 KiB  | 
@@ -1,3 +0,0 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/wwii-ship-uk-king-george-v-class-battleship-v1--185381.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
@@ -1,103 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship;
 | 
			
		||||
 | 
			
		||||
import pp.util.config.Config;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.TreeMap;
 | 
			
		||||
 | 
			
		||||
import static java.lang.Math.max;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides access to the configuration settings for the Battleship game.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This class allows for loading configuration settings from a properties file,
 | 
			
		||||
 * including the server port, map dimensions, and the number of ships of various lengths.
 | 
			
		||||
 * </p>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * <b>Note:</b> Attributes of this class are not marked as {@code final} to allow
 | 
			
		||||
 * for proper initialization when reading from a properties file.
 | 
			
		||||
 * </p>
 | 
			
		||||
 */
 | 
			
		||||
public class BattleshipConfig extends Config {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The default port number for the Battleship server.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("port")
 | 
			
		||||
    private int port = 1234;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The width of the game map in terms of grid units.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("map.width")
 | 
			
		||||
    private int mapWidth = 10;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The height of the game map in terms of grid units.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("map.height")
 | 
			
		||||
    private int mapHeight = 10;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An array representing the number of ships available for each length.
 | 
			
		||||
     * The index corresponds to the ship length minus one, and the value at each index
 | 
			
		||||
     * is the number of ships of that length.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("ship.nums")
 | 
			
		||||
    private int[] shipNums = {4, 3, 2, 1};
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an instance of {@code BattleshipConfig} with default settings.
 | 
			
		||||
     */
 | 
			
		||||
    public BattleshipConfig() {
 | 
			
		||||
        // Default constructor
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the port number configured for the Battleship server.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the port number
 | 
			
		||||
     */
 | 
			
		||||
    public int getPort() {
 | 
			
		||||
        return port;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the width of the game map. The width is guaranteed to be at least 2 units.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the width of the game map
 | 
			
		||||
     */
 | 
			
		||||
    public int getMapWidth() {
 | 
			
		||||
        return max(mapWidth, 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the height of the game map. The height is guaranteed to be at least 2 units.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the height of the game map
 | 
			
		||||
     */
 | 
			
		||||
    public int getMapHeight() {
 | 
			
		||||
        return max(mapHeight, 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a map representing the number of ships for each length.
 | 
			
		||||
     * The keys are ship lengths, and the values are the corresponding number of ships.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a map of ship lengths to the number of ships
 | 
			
		||||
     */
 | 
			
		||||
    public Map<Integer, Integer> getShipNums() {
 | 
			
		||||
        final TreeMap<Integer, Integer> ships = new TreeMap<>();
 | 
			
		||||
        for (int i = 0; i < shipNums.length; i++)
 | 
			
		||||
            if (shipNums[i] > 0)
 | 
			
		||||
                ships.put(i + 1, shipNums[i]);
 | 
			
		||||
        return ships;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship;
 | 
			
		||||
 | 
			
		||||
import java.util.ResourceBundle;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides access to the resource bundle of the game.
 | 
			
		||||
 *
 | 
			
		||||
 * @see #BUNDLE
 | 
			
		||||
 */
 | 
			
		||||
public class Resources {
 | 
			
		||||
    /**
 | 
			
		||||
     * The resource bundle for the Battleship game.
 | 
			
		||||
     */
 | 
			
		||||
    public static final ResourceBundle BUNDLE = ResourceBundle.getBundle("battleship"); //NON-NLS
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a string for the given key from the resource bundle in {@linkplain #BUNDLE}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key the key for the desired string
 | 
			
		||||
     * @return the string for the given key
 | 
			
		||||
     * @throws NullPointerException               if {@code key} is {@code null}
 | 
			
		||||
     * @throws java.util.MissingResourceException if no object for the given key can be found
 | 
			
		||||
     * @throws ClassCastException                 if the object found for the given key is not a string
 | 
			
		||||
     */
 | 
			
		||||
    public static String lookup(String key) {
 | 
			
		||||
        return BUNDLE.getString(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Private constructor to prevent instantiation.
 | 
			
		||||
     */
 | 
			
		||||
    private Resources() { /* do not instantiate */ }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,159 +0,0 @@
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.AnimationMessage;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.Shell;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.notification.Music;
 | 
			
		||||
import pp.battleship.notification.Sound;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the game during an animation sequence.
 | 
			
		||||
 * This state handles the progress and completion of the animation,
 | 
			
		||||
 * updates the game state accordingly, and transitions to the next state.
 | 
			
		||||
 */
 | 
			
		||||
public class AnimationState extends ClientState {
 | 
			
		||||
    /**
 | 
			
		||||
     * Progress of the current animation, ranging from 0 to 1.
 | 
			
		||||
     */
 | 
			
		||||
    private float animationProgress = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Duration of the animation in seconds.
 | 
			
		||||
     */
 | 
			
		||||
    private final static float ANIMATION_DURATION = 0.375f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Speed of the shell in the animation.
 | 
			
		||||
     */
 | 
			
		||||
    private final static float SHELL_SPEED = 0.3f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The effect message received from the server.
 | 
			
		||||
     */
 | 
			
		||||
    private final EffectMessage msg;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The shell involved in the animation.
 | 
			
		||||
     */
 | 
			
		||||
    private final Shell shell;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an AnimationState with the specified game logic, effect message, and shell.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the game logic associated with this state
 | 
			
		||||
     * @param msg   the effect message received from the server
 | 
			
		||||
     * @param shell the shell involved in the animation
 | 
			
		||||
     */
 | 
			
		||||
    public AnimationState(ClientGameLogic logic, EffectMessage msg, Shell shell) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
        this.msg = msg;
 | 
			
		||||
        this.shell = shell;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Ends the animation state and transitions to the next state:<br>
 | 
			
		||||
     * - Plays the appropriate sound.<br>
 | 
			
		||||
     * - Updates the affected map.<br>
 | 
			
		||||
     * - Adds destroyed ships to the opponent's map.<br>
 | 
			
		||||
     * - Sends an `AnimationMessage` to the server.<br>
 | 
			
		||||
     * - If the game is over, transitions to `GameOverState` and plays music.<br>
 | 
			
		||||
     * - Otherwise, transitions to `BattleState`.
 | 
			
		||||
     */
 | 
			
		||||
    public void endState() {
 | 
			
		||||
        playSound(msg);
 | 
			
		||||
        affectedMap(msg).add(msg.getShot());
 | 
			
		||||
        affectedMap(msg).remove(shell);
 | 
			
		||||
 | 
			
		||||
        if (destroyedOpponentShip(msg))
 | 
			
		||||
            logic.getOpponentMap().add(msg.getDestroyedShip());
 | 
			
		||||
 | 
			
		||||
        logic.send(new AnimationMessage());
 | 
			
		||||
        if (msg.isGameOver()) {
 | 
			
		||||
            for (Battleship ship : msg.getRemainingOpponentShips()) {
 | 
			
		||||
                logic.getOpponentMap().add(ship);
 | 
			
		||||
            }
 | 
			
		||||
            logic.setState(new GameOverState(logic));
 | 
			
		||||
            if (msg.isOwnShot())
 | 
			
		||||
                logic.playMusic(Music.VICTORY_MUSIC);
 | 
			
		||||
            else
 | 
			
		||||
                logic.playMusic(Music.DEFEAT_MUSIC);
 | 
			
		||||
        } else {
 | 
			
		||||
            logic.setState(new BattleState(logic, msg.isMyTurn()));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the battle state should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battle state should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showBattle() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines which map (own or opponent's) should be affected by the shot based on the message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the effect message received from the server
 | 
			
		||||
     * @return the map (either the opponent's or player's own map) that is affected by the shot
 | 
			
		||||
     */
 | 
			
		||||
    private ShipMap affectedMap(EffectMessage msg) {
 | 
			
		||||
        return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the opponent's ship was destroyed by the player's shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the effect message received from the server
 | 
			
		||||
     * @return true if the shot destroyed an opponent's ship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    private boolean destroyedOpponentShip(EffectMessage msg) {
 | 
			
		||||
        return msg.getDestroyedShip() != null && msg.isOwnShot();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays a sound based on the outcome of the shot. Different sounds are played for a miss, hit,
 | 
			
		||||
     * or destruction of a ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the effect message containing the result of the shot
 | 
			
		||||
     */
 | 
			
		||||
    private void playSound(EffectMessage msg) {
 | 
			
		||||
        if (!msg.getShot().isHit())
 | 
			
		||||
            logic.playSound(Sound.SPLASH);
 | 
			
		||||
        else if (msg.getDestroyedShip() == null)
 | 
			
		||||
            logic.playSound(Sound.EXPLOSION);
 | 
			
		||||
        else
 | 
			
		||||
            logic.playSound(Sound.DESTROYED_SHIP);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clickOpponentMap(IntPoint pos) {
 | 
			
		||||
        if (!msg.isMyTurn())
 | 
			
		||||
            logic.setInfoText("wait.its.not.your.turn");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the state of the animation. This method increments the animationProgress value
 | 
			
		||||
     * until it exceeds a threshold, at which point the state ends.
 | 
			
		||||
     *
 | 
			
		||||
     * @param delta the time elapsed since the last update, in seconds
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void update(float delta) {
 | 
			
		||||
        if (animationProgress > ANIMATION_DURATION) {
 | 
			
		||||
            endState();
 | 
			
		||||
        } else {
 | 
			
		||||
            animationProgress += delta * SHELL_SPEED;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,85 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.Shell;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.notification.Sound;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the client where players take turns to attack each other's ships.
 | 
			
		||||
 */
 | 
			
		||||
class BattleState extends ClientState {
 | 
			
		||||
    private boolean myTurn;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new instance of {@link BattleState}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic  the game logic
 | 
			
		||||
     * @param myTurn true if it is my turn
 | 
			
		||||
     */
 | 
			
		||||
    public BattleState(ClientGameLogic logic, boolean myTurn) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
        this.myTurn = myTurn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the battle state should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battle state should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showBattle() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clickOpponentMap(IntPoint pos) {
 | 
			
		||||
        if (!myTurn)
 | 
			
		||||
            logic.setInfoText("wait.its.not.your.turn");
 | 
			
		||||
        else if (logic.getOpponentMap().isValid(pos))
 | 
			
		||||
            logic.send(new ShootMessage(pos));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reports the effect of a shot based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message containing the effect of the shot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEffect(EffectMessage msg) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(System.Logger.Level.INFO, "report effect: {0}", msg); //NON-NLS
 | 
			
		||||
        // Update turn and info text
 | 
			
		||||
        myTurn = msg.isMyTurn();
 | 
			
		||||
        logic.setInfoText(msg.getInfoTextKey());
 | 
			
		||||
        // Add the shell to the affected map
 | 
			
		||||
        Shell shell = new Shell(msg.getShot());
 | 
			
		||||
        affectedMap(msg).add(shell);
 | 
			
		||||
        // Change state to AnimationState
 | 
			
		||||
        logic.playSound(Sound.SHELL_FIRED);
 | 
			
		||||
        logic.setState(new AnimationState(logic, msg, shell));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines which map (own or opponent's) should be affected by the shot based on the message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the effect message received from the server
 | 
			
		||||
     * @return the map (either the opponent's or player's own map) that is affected by the shot
 | 
			
		||||
     */
 | 
			
		||||
    private ShipMap affectedMap(EffectMessage msg) {
 | 
			
		||||
        return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,38 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.game.singlemode.BattleshipClientConfig;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface representing a Battleship client.
 | 
			
		||||
 * Provides methods to access game logic, configuration, and to enqueue tasks.
 | 
			
		||||
 */
 | 
			
		||||
public interface BattleshipClient {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the game logic associated with this client.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the ClientGameLogic instance
 | 
			
		||||
     */
 | 
			
		||||
    ClientGameLogic getGameLogic();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the configuration associated with this client.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the BattleshipConfig instance
 | 
			
		||||
     */
 | 
			
		||||
    BattleshipClientConfig getConfig();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enqueues a task to be executed by the client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param runnable the task to be executed
 | 
			
		||||
     */
 | 
			
		||||
    void enqueue(Runnable runnable);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,358 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.message.server.ServerInterpreter;
 | 
			
		||||
import pp.battleship.message.server.StartBattleMessage;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.model.dto.ShipMapDTO;
 | 
			
		||||
import pp.battleship.notification.*;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static java.lang.Math.max;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls the client-side game logic for Battleship.
 | 
			
		||||
 * Manages the player's ship placement, interactions with the map, and response to server messages.
 | 
			
		||||
 */
 | 
			
		||||
public class ClientGameLogic implements ServerInterpreter, GameEventBroker {
 | 
			
		||||
    static final Logger LOGGER = System.getLogger(ClientGameLogic.class.getName());
 | 
			
		||||
    private final ClientSender clientSender;
 | 
			
		||||
    private final List<GameEventListener> listeners = new ArrayList<>();
 | 
			
		||||
    private GameDetails details;
 | 
			
		||||
    private ShipMap ownMap;
 | 
			
		||||
    private ShipMap harbor;
 | 
			
		||||
    private ShipMap opponentMap;
 | 
			
		||||
    private ClientState state = new InitialState(this);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a ClientGameLogic with the specified sender object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param clientSender the object used to send messages to the server
 | 
			
		||||
     */
 | 
			
		||||
    public ClientGameLogic(ClientSender clientSender) {
 | 
			
		||||
        this.clientSender = clientSender;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the current state of the game logic.
 | 
			
		||||
     */
 | 
			
		||||
    ClientState getState() {
 | 
			
		||||
        return state;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the current state of the game logic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param newState the new state to be set
 | 
			
		||||
     */
 | 
			
		||||
    void setState(ClientState newState) {
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "state transition {0} --> {1}", state.getName(), newState.getName()); //NON-NLS
 | 
			
		||||
        state = newState;
 | 
			
		||||
        notifyListeners(new ClientStateEvent());
 | 
			
		||||
        state.entry();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the game details.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the game details
 | 
			
		||||
     */
 | 
			
		||||
    GameDetails getDetails() {
 | 
			
		||||
        return details;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the player's own map
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMap getOwnMap() {
 | 
			
		||||
        return ownMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the opponent's map
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMap getOpponentMap() {
 | 
			
		||||
        return opponentMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the harbor map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the harbor map
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMap getHarbor() {
 | 
			
		||||
        return harbor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the editor should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the editor should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean showEditor() {
 | 
			
		||||
        return state.showEditor();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the battle state should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battle state should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean showBattle() {
 | 
			
		||||
        return state.showBattle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the game details provided by the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details including map size and ships
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(GameDetails details) {
 | 
			
		||||
        state.receivedGameDetails(details);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves the preview ship to the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the new position for the preview ship
 | 
			
		||||
     */
 | 
			
		||||
    public void movePreview(IntPoint pos) {
 | 
			
		||||
        state.movePreview(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    public void clickOwnMap(IntPoint pos) {
 | 
			
		||||
        state.clickOwnMap(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the harbor map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    public void clickHarbor(IntPoint pos) {
 | 
			
		||||
        state.clickHarbor(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    public void clickOpponentMap(IntPoint pos) {
 | 
			
		||||
        state.clickOpponentMap(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rotates the preview ship.
 | 
			
		||||
     */
 | 
			
		||||
    public void rotateShip() {
 | 
			
		||||
        state.rotateShip();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Marks the player's map as finished.
 | 
			
		||||
     */
 | 
			
		||||
    public void mapFinished() {
 | 
			
		||||
        state.mapFinished();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's map is complete (i.e., all ships are placed).
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if all ships are placed, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isMapComplete() {
 | 
			
		||||
        return state.isMapComplete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if there is currently a preview ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if there is currently a preview ship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean movingShip() {
 | 
			
		||||
        return state.movingShip();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the battle based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message indicating whose turn it is to shoot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(StartBattleMessage msg) {
 | 
			
		||||
        state.receivedStartBattle(msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reports the effect of a shot based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message containing the effect of the shot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(EffectMessage msg) {
 | 
			
		||||
        state.receivedEffect(msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the player's own map, opponent's map, and harbor based on the game details.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details including map size and ships
 | 
			
		||||
     */
 | 
			
		||||
    void initializeMaps(GameDetails details) {
 | 
			
		||||
        this.details = details;
 | 
			
		||||
        final int numShips = details.getShipNums().values().stream().mapToInt(Integer::intValue).sum();
 | 
			
		||||
        final int maxLength = details.getShipNums().keySet().stream().mapToInt(Integer::intValue).max().orElse(2);
 | 
			
		||||
        ownMap = new ShipMap(details.getWidth(), details.getHeight(), this);
 | 
			
		||||
        opponentMap = new ShipMap(details.getWidth(), details.getHeight(), this);
 | 
			
		||||
        harbor = new ShipMap(max(maxLength, 2), max(numShips, details.getHeight()), this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the informational text to be displayed to the player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key the key for the info text
 | 
			
		||||
     */
 | 
			
		||||
    void setInfoText(String key) {
 | 
			
		||||
        notifyListeners(new InfoTextEvent(key));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emits an event to play the specified sound.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sound the sound to be played.
 | 
			
		||||
     */
 | 
			
		||||
    public void playSound(Sound sound) {
 | 
			
		||||
        notifyListeners(new SoundEvent(sound));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emits an event to play the specified music.
 | 
			
		||||
     *
 | 
			
		||||
     * @param music the music to be played.
 | 
			
		||||
     */
 | 
			
		||||
    public void playMusic(Music music) {
 | 
			
		||||
        notifyListeners(new MusicEvent(music));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a map from the specified file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param file the file to load the map from
 | 
			
		||||
     * @throws IOException if an I/O error occurs
 | 
			
		||||
     */
 | 
			
		||||
    public void loadMap(File file) throws IOException {
 | 
			
		||||
        state.loadMap(file);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's own map may be loaded from a file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be loaded from file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean mayLoadMap() {
 | 
			
		||||
        return state.mayLoadMap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's own map may be saved to a file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be saved to file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean maySaveMap() {
 | 
			
		||||
        return state.maySaveMap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Saves the player's own map to the specified file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param file the file to save the map to
 | 
			
		||||
     * @throws IOException if the map cannot be saved in the current state
 | 
			
		||||
     */
 | 
			
		||||
    public void saveMap(File file) throws IOException {
 | 
			
		||||
        if (ownMap != null && maySaveMap())
 | 
			
		||||
            new ShipMapDTO(ownMap).saveTo(file);
 | 
			
		||||
        else
 | 
			
		||||
            throw new IOException("You are not allowed to save the map in this state of the game");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a message to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message to be sent
 | 
			
		||||
     */
 | 
			
		||||
    void send(ClientMessage msg) {
 | 
			
		||||
        if (clientSender == null)
 | 
			
		||||
            LOGGER.log(Level.ERROR, "trying to send {0} with sender==null", msg); //NON-NLS
 | 
			
		||||
        else
 | 
			
		||||
            clientSender.send(msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a listener to receive game events.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener the listener to add
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized void addListener(GameEventListener listener) {
 | 
			
		||||
        listeners.add(listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a listener from receiving game events.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener the listener to remove
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized void removeListener(GameEventListener listener) {
 | 
			
		||||
        listeners.remove(listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies all listeners of a game event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the game event to notify listeners of
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void notifyListeners(GameEvent event) {
 | 
			
		||||
        final List<GameEventListener> copy;
 | 
			
		||||
        synchronized (this) {
 | 
			
		||||
            copy = new ArrayList<>(listeners);
 | 
			
		||||
        }
 | 
			
		||||
        for (GameEventListener listener : copy)
 | 
			
		||||
            event.notifyListener(listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called once per frame by the update loop.
 | 
			
		||||
     *
 | 
			
		||||
     * @param delta time in seconds since the last update call
 | 
			
		||||
     */
 | 
			
		||||
    public void update(float delta) {
 | 
			
		||||
        state.update(delta);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface for sending messages to the server.
 | 
			
		||||
 */
 | 
			
		||||
public interface ClientSender {
 | 
			
		||||
    /**
 | 
			
		||||
     * Send the specified message to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message the message
 | 
			
		||||
     */
 | 
			
		||||
    void send(ClientMessage message);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,202 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.message.server.StartBattleMessage;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the behavior and state transitions for the client-side game logic.
 | 
			
		||||
 * Different states of the game logic implement this interface to handle various game events and actions.
 | 
			
		||||
 */
 | 
			
		||||
abstract class ClientState {
 | 
			
		||||
    /**
 | 
			
		||||
     * The game logic object.
 | 
			
		||||
     */
 | 
			
		||||
    final ClientGameLogic logic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a client state of the specified game logic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the game logic
 | 
			
		||||
     */
 | 
			
		||||
    ClientState(ClientGameLogic logic) {
 | 
			
		||||
        this.logic = logic;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to be overridden by subclasses for post-transition initialization.
 | 
			
		||||
     * By default, it does nothing, but it can be overridden in derived states.
 | 
			
		||||
     */
 | 
			
		||||
    void entry() {
 | 
			
		||||
        // Default implementation does nothing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the name of the current state.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the name of the current state
 | 
			
		||||
     */
 | 
			
		||||
    String getName() {
 | 
			
		||||
        return getClass().getSimpleName();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the editor should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the editor should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean showEditor() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the battle state should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battle state should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean showBattle() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's map is complete (i.e., all ships are placed).
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if all ships are placed, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean isMapComplete() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if there is currently a preview ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if there is currently a preview ship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean movingShip() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    void clickOwnMap(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "clickOwnMap has no effect in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the harbor map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    void clickHarbor(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "clickHarbor has no effect in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    void clickOpponentMap(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "clickOpponentMap has no effect in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves the preview ship to the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the new position for the preview ship
 | 
			
		||||
     */
 | 
			
		||||
    void movePreview(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "movePreview has no effect in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rotates the preview ship.
 | 
			
		||||
     */
 | 
			
		||||
    void rotateShip() {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "rotateShip has no effect in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user has marked the map as finished.
 | 
			
		||||
     */
 | 
			
		||||
    void mapFinished() {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.ERROR, "mapFinished not allowed in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the game details provided by the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details including map size and ships
 | 
			
		||||
     */
 | 
			
		||||
    void receivedGameDetails(GameDetails details) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.ERROR, "receivedGameDetails not allowed in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the battle based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message indicating whose turn it is to shoot
 | 
			
		||||
     */
 | 
			
		||||
    void receivedStartBattle(StartBattleMessage msg) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.ERROR, "receivedStartBattle not allowed in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reports the effect of a shot based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message containing the effect of the shot
 | 
			
		||||
     */
 | 
			
		||||
    void receivedEffect(EffectMessage msg) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.ERROR, "receivedEffect not allowed in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a map from the specified file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param file the file to load the map from
 | 
			
		||||
     * @throws IOException if the map cannot be loaded in the current state
 | 
			
		||||
     */
 | 
			
		||||
    void loadMap(File file) throws IOException {
 | 
			
		||||
        throw new IOException("You are not allowed to load a map in this state of the game");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the own map may be loaded from file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be loaded from file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean mayLoadMap() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the own map may be saved to file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be saved to file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean maySaveMap() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called once per frame by the update loop if this state is active.
 | 
			
		||||
     *
 | 
			
		||||
     * @param delta time in seconds since the last update call
 | 
			
		||||
     */
 | 
			
		||||
    void update(float delta) { /* do nothing by default */ }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,334 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.MapMessage;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.model.dto.ShipMapDTO;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.Resources.lookup;
 | 
			
		||||
import static pp.battleship.model.Battleship.Status.INVALID_PREVIEW;
 | 
			
		||||
import static pp.battleship.model.Battleship.Status.NORMAL;
 | 
			
		||||
import static pp.battleship.model.Battleship.Status.VALID_PREVIEW;
 | 
			
		||||
import static pp.battleship.model.Rotation.RIGHT;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the client setting up the ship map.
 | 
			
		||||
 */
 | 
			
		||||
class EditorState extends ClientState {
 | 
			
		||||
    private Battleship preview;
 | 
			
		||||
    private Battleship selectedInHarbor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new EditorState with the specified ClientGameLogic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the ClientGameLogic associated with this state
 | 
			
		||||
     */
 | 
			
		||||
    public EditorState(ClientGameLogic logic) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns true to indicate that the editor should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the editor should be shown
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showEditor() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves the preview ship to the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the new position for the preview ship
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void movePreview(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "move preview to {0}", pos); //NON-NLS
 | 
			
		||||
        if (preview == null || !ownMap().isValid(pos)) return;
 | 
			
		||||
        preview.moveTo(pos);
 | 
			
		||||
        setPreviewStatus(preview);
 | 
			
		||||
        ownMap().remove(preview);
 | 
			
		||||
        ownMap().add(preview);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clickOwnMap(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "click at {0} in own map", pos); //NON-NLS
 | 
			
		||||
        if (!ownMap().isValid(pos)) return;
 | 
			
		||||
        if (preview == null)
 | 
			
		||||
            modifyShip(pos);
 | 
			
		||||
        else
 | 
			
		||||
            placeShip(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Modifies a ship on the map at the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param cursor the position of the ship to modify
 | 
			
		||||
     */
 | 
			
		||||
    private void modifyShip(IntPoint cursor) {
 | 
			
		||||
        preview = ownMap().findShipAt(cursor);
 | 
			
		||||
        if (preview == null)
 | 
			
		||||
            return;
 | 
			
		||||
        preview.moveTo(cursor);
 | 
			
		||||
        setPreviewStatus(preview);
 | 
			
		||||
        ownMap().remove(preview);
 | 
			
		||||
        ownMap().add(preview);
 | 
			
		||||
        selectedInHarbor = new Battleship(preview.getLength(), 0, freeY(), RIGHT);
 | 
			
		||||
        selectedInHarbor.setStatus(VALID_PREVIEW);
 | 
			
		||||
        harbor().add(selectedInHarbor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Places the preview ship at the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param cursor the position to place the ship
 | 
			
		||||
     */
 | 
			
		||||
    private void placeShip(IntPoint cursor) {
 | 
			
		||||
        ownMap().remove(preview);
 | 
			
		||||
        preview.moveTo(cursor);
 | 
			
		||||
        if (ownMap().isValid(preview)) {
 | 
			
		||||
            preview.setStatus(NORMAL);
 | 
			
		||||
            ownMap().add(preview);
 | 
			
		||||
            harbor().remove(selectedInHarbor);
 | 
			
		||||
            preview = null;
 | 
			
		||||
            selectedInHarbor = null;
 | 
			
		||||
        } else {
 | 
			
		||||
            preview.setStatus(INVALID_PREVIEW);
 | 
			
		||||
            ownMap().add(preview);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the harbor map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clickHarbor(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "click at {0} in harbor", pos); //NON-NLS
 | 
			
		||||
        if (!harbor().isValid(pos)) return;
 | 
			
		||||
        final Battleship shipAtCursor = harbor().findShipAt(pos);
 | 
			
		||||
        if (preview != null) {
 | 
			
		||||
            ownMap().remove(preview);
 | 
			
		||||
            selectedInHarbor.setStatus(NORMAL);
 | 
			
		||||
            harbor().remove(selectedInHarbor);
 | 
			
		||||
            harbor().add(selectedInHarbor);
 | 
			
		||||
            preview = null;
 | 
			
		||||
            selectedInHarbor = null;
 | 
			
		||||
        } else if (shipAtCursor != null) {
 | 
			
		||||
            selectedInHarbor = shipAtCursor;
 | 
			
		||||
            selectedInHarbor.setStatus(VALID_PREVIEW);
 | 
			
		||||
            harbor().remove(selectedInHarbor);
 | 
			
		||||
            harbor().add(selectedInHarbor);
 | 
			
		||||
            preview = new Battleship(selectedInHarbor.getLength(), 0, 0, RIGHT);
 | 
			
		||||
            setPreviewStatus(preview);
 | 
			
		||||
            ownMap().add(preview);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rotates the preview ship.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void rotateShip() {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "pushed rotate"); //NON-NLS
 | 
			
		||||
        if (preview == null) return;
 | 
			
		||||
        preview.rotated();
 | 
			
		||||
        ownMap().remove(preview);
 | 
			
		||||
        ownMap().add(preview);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Finds a free position in the harbor to place a ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the y coordinate of a free position in the harbor
 | 
			
		||||
     */
 | 
			
		||||
    private int freeY() {
 | 
			
		||||
        for (int i = 0; i < harbor().getHeight(); i++)
 | 
			
		||||
            if (harbor().findShipAt(0, i) == null)
 | 
			
		||||
                return i;
 | 
			
		||||
        throw new RuntimeException("Cannot find a free slot in harbor");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the status of the specified ship based on its validity.
 | 
			
		||||
     */
 | 
			
		||||
    private void setPreviewStatus(Battleship ship) {
 | 
			
		||||
        ship.setStatus(ownMap().isValid(ship) ? VALID_PREVIEW : INVALID_PREVIEW);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user has marked the map as finished.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void mapFinished() {
 | 
			
		||||
        if (!harbor().getItems().isEmpty()) return;
 | 
			
		||||
        logic.send(new MapMessage(ownMap().getRemainingShips()));
 | 
			
		||||
        logic.setInfoText("wait.for.opponent");
 | 
			
		||||
        logic.setState(new WaitState(logic));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's map is complete (i.e., all ships are placed).
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if all ships are placed, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isMapComplete() {
 | 
			
		||||
        return harbor().getItems().isEmpty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if there is currently a preview ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if there is currently a preview ship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean movingShip() {
 | 
			
		||||
        return preview != null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the player's own map
 | 
			
		||||
     */
 | 
			
		||||
    private ShipMap ownMap() {
 | 
			
		||||
        return logic.getOwnMap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the harbor map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the harbor map
 | 
			
		||||
     */
 | 
			
		||||
    private ShipMap harbor() {
 | 
			
		||||
        return logic.getHarbor();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a map from the specified file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param file the file to load the map from
 | 
			
		||||
     * @throws IOException if the map cannot be loaded
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadMap(File file) throws IOException {
 | 
			
		||||
        final ShipMapDTO dto = ShipMapDTO.loadFrom(file);
 | 
			
		||||
        if (!dto.fits(logic.getDetails()))
 | 
			
		||||
            throw new IOException(lookup("map.doesnt.fit"));
 | 
			
		||||
        if (!validMap(dto))
 | 
			
		||||
            throw new IOException(lookup("map.invalid"));
 | 
			
		||||
        ownMap().clear();
 | 
			
		||||
        dto.getShips().forEach(ownMap()::add);
 | 
			
		||||
        harbor().clear();
 | 
			
		||||
        preview = null;
 | 
			
		||||
        selectedInHarbor = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's own map may be loaded from a file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be loaded from file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean mayLoadMap() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's own map may be saved to a file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be saved to file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean maySaveMap() {
 | 
			
		||||
        return harbor().getItems().isEmpty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validates the given ShipMapDTO by checking if all ships are within bounds
 | 
			
		||||
     * and do not overlap with each other.
 | 
			
		||||
     *
 | 
			
		||||
     * @param dto the ShipMapDTO to validate
 | 
			
		||||
     * @return true if the map is valid, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean validMap(ShipMapDTO dto) {
 | 
			
		||||
        return inBounds(dto) && !overlaps(dto);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if all ships in the given ShipMapDTO are within the bounds of the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param dto the ShipMapDTO to validate
 | 
			
		||||
     * @return true if all ships are within bounds, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean inBounds(ShipMapDTO dto) {
 | 
			
		||||
        List<Battleship> ships = dto.getShips();
 | 
			
		||||
        for (Battleship ship : ships) {
 | 
			
		||||
            if (!isWithinBounds(ship, dto.getWidth(), dto.getHeight())) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the given ship is within the bounds of the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship   the Battleship to check
 | 
			
		||||
     * @param width  the width of the map
 | 
			
		||||
     * @param height the height of the map
 | 
			
		||||
     * @return true if the ship is within bounds, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean isWithinBounds(Battleship ship, int width, int height) {
 | 
			
		||||
        int minX = ship.getMinX();
 | 
			
		||||
        int maxX = ship.getMaxX();
 | 
			
		||||
        int minY = ship.getMinY();
 | 
			
		||||
        int maxY = ship.getMaxY();
 | 
			
		||||
        return minX >= 0 && minX < width &&
 | 
			
		||||
                minY >= 0 && minY < height &&
 | 
			
		||||
                maxX >= 0 && maxX < width &&
 | 
			
		||||
                maxY >= 0 && maxY < height;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if any ships in the given ShipMapDTO overlap with each other.
 | 
			
		||||
     *
 | 
			
		||||
     * @param dto the ShipMapDTO to validate
 | 
			
		||||
     * @return true if any ships overlap, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean overlaps(ShipMapDTO dto) {
 | 
			
		||||
        List<Battleship> ships = dto.getShips();
 | 
			
		||||
        for (int i = 0; i < ships.size(); i++) {
 | 
			
		||||
            Battleship ship1 = ships.get(i);
 | 
			
		||||
            for (int j = i + 1; j < ships.size(); j++) {
 | 
			
		||||
                Battleship ship2 = ships.get(j);
 | 
			
		||||
                if (ship1.collidesWith(ship2)) {
 | 
			
		||||
                    return true; // Collision detected
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the client when the game is over.
 | 
			
		||||
 */
 | 
			
		||||
class GameOverState extends ClientState {
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new instance of GameOverState.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the client game logic
 | 
			
		||||
     */
 | 
			
		||||
    GameOverState(ClientGameLogic logic) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns true to indicate that the battle state should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battle state should be shown
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showBattle() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,65 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.Rotation;
 | 
			
		||||
 | 
			
		||||
import java.util.Map.Entry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the client waiting for the
 | 
			
		||||
 * {@linkplain pp.battleship.message.server.GameDetails}
 | 
			
		||||
 * from the server.
 | 
			
		||||
 */
 | 
			
		||||
class InitialState extends ClientState {
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new initial state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the game logic
 | 
			
		||||
     */
 | 
			
		||||
    public InitialState(ClientGameLogic logic) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the game details provided by the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details including map size and ships
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedGameDetails(GameDetails details) {
 | 
			
		||||
        logic.initializeMaps(details);
 | 
			
		||||
        fillHarbor(details);
 | 
			
		||||
        logic.setInfoText(details.getInfoTextKey());
 | 
			
		||||
        logic.setState(new EditorState(logic));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fills the harbor with ships as specified by the game details.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details including map size and ships
 | 
			
		||||
     */
 | 
			
		||||
    private void fillHarbor(GameDetails details) {
 | 
			
		||||
        int y = 0;
 | 
			
		||||
        for (Entry<Integer, Integer> entry : details.getShipNums().entrySet()) {
 | 
			
		||||
            final int len = entry.getKey();
 | 
			
		||||
            final int num = entry.getValue();
 | 
			
		||||
            for (int i = 0; i < num; i++) {
 | 
			
		||||
                final Battleship ship = new Battleship(len, 0, y++, Rotation.RIGHT);
 | 
			
		||||
                logic.getHarbor().add(ship);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean maySaveMap() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface representing a connection to the server.
 | 
			
		||||
 * Extends ClientSender to allow sending messages to the server.
 | 
			
		||||
 */
 | 
			
		||||
public interface ServerConnection extends ClientSender {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the client is currently connected to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if connected, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    boolean isConnected();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Establishes a connection to the server.
 | 
			
		||||
     */
 | 
			
		||||
    void connect();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disconnects from the server.
 | 
			
		||||
     */
 | 
			
		||||
    void disconnect();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,57 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.message.server.StartBattleMessage;
 | 
			
		||||
import pp.battleship.notification.Music;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
class WaitState extends ClientState {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new instance of {@link WaitState}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the game logic
 | 
			
		||||
     */
 | 
			
		||||
    public WaitState(ClientGameLogic logic) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showEditor() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the battle based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message indicating whose turn it is to shoot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedStartBattle(StartBattleMessage msg) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.INFO, "start battle, {0} turn", msg.isMyTurn() ? "my" : "other's"); //NON-NLS
 | 
			
		||||
        logic.setInfoText(msg.getInfoTextKey());
 | 
			
		||||
        logic.setState(new BattleState(logic, msg.isMyTurn()));
 | 
			
		||||
        logic.playMusic(Music.GAME_MUSIC);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the GameDetails message received from the server.
 | 
			
		||||
     * If the map is invalid, the editor state is set.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the GameDetails message received
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedGameDetails(GameDetails msg) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.WARNING, "Invalid Map"); //NON-NLS
 | 
			
		||||
        logic.setInfoText("map.invalid");
 | 
			
		||||
        logic.setState(new EditorState(logic));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,54 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.server;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.BattleshipConfig;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class representing a player
 | 
			
		||||
 */
 | 
			
		||||
public class Player {
 | 
			
		||||
    private final ShipMap map;
 | 
			
		||||
    private final String name;
 | 
			
		||||
    private final int id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates new Player
 | 
			
		||||
     *
 | 
			
		||||
     * @param id     the id of the connection to the client represented by this player
 | 
			
		||||
     * @param name   the human-readable name of this player
 | 
			
		||||
     * @param config model holding the player
 | 
			
		||||
     */
 | 
			
		||||
    Player(int id, String name, BattleshipConfig config) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
        this.name = name;
 | 
			
		||||
        map = new ShipMap(config.getMapWidth(), config.getMapHeight(), null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the id of the connection to the client represented by this player.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the id
 | 
			
		||||
     */
 | 
			
		||||
    public int getId() {
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return String.format("Player(%s,%s)", name, id); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return map containing own ships and shots
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMap getMap() {
 | 
			
		||||
        return map;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,320 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.server;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.BattleshipConfig;
 | 
			
		||||
import pp.battleship.message.client.AnimationMessage;
 | 
			
		||||
import pp.battleship.message.client.ClientInterpreter;
 | 
			
		||||
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 java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls the server-side game logic for Battleship.
 | 
			
		||||
 * Manages game states, player interactions, and message handling.
 | 
			
		||||
 */
 | 
			
		||||
public class ServerGameLogic implements ClientInterpreter {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(ServerGameLogic.class.getName());
 | 
			
		||||
 | 
			
		||||
    private final BattleshipConfig config;
 | 
			
		||||
    private final List<Player> players = new ArrayList<>(2);
 | 
			
		||||
    private final Set<Player> readyPlayers = new HashSet<>();
 | 
			
		||||
    private final Set<Player> finishedAnimation = new HashSet<>();
 | 
			
		||||
    private final ServerSender serverSender;
 | 
			
		||||
    private Player activePlayer;
 | 
			
		||||
    private ServerState state = ServerState.WAIT;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a ServerGameLogic with the specified sender and configuration.
 | 
			
		||||
     *
 | 
			
		||||
     * @param serverSender the sender used to send messages to clients
 | 
			
		||||
     * @param config       the game configuration
 | 
			
		||||
     */
 | 
			
		||||
    public ServerGameLogic(ServerSender serverSender, BattleshipConfig config) {
 | 
			
		||||
        this.serverSender = serverSender;
 | 
			
		||||
        this.config = config;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the  state of the game.
 | 
			
		||||
     */
 | 
			
		||||
    ServerState getState() {
 | 
			
		||||
        return state;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the new state of the game and logs the state transition.
 | 
			
		||||
     *
 | 
			
		||||
     * @param newState the new state to set
 | 
			
		||||
     */
 | 
			
		||||
    void setState(ServerState newState) {
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "state transition {0} --> {1}", state, newState); //NON-NLS
 | 
			
		||||
        state = newState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the opponent of the specified player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param p the player
 | 
			
		||||
     * @return the opponent of the player
 | 
			
		||||
     */
 | 
			
		||||
    Player getOpponent(Player p) {
 | 
			
		||||
        if (players.size() != 2)
 | 
			
		||||
            throw new RuntimeException("trying to find opponent without having 2 players");
 | 
			
		||||
        final int index = players.indexOf(p);
 | 
			
		||||
        if (index < 0)
 | 
			
		||||
            throw new RuntimeException("Nonexistent player " + p);
 | 
			
		||||
        return players.get(1 - index);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the player representing the client with the specified connection ID.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id the ID of the client
 | 
			
		||||
     * @return the player associated with the client ID, or null if not found
 | 
			
		||||
     */
 | 
			
		||||
    public Player getPlayerById(int id) {
 | 
			
		||||
        for (Player player : players)
 | 
			
		||||
            if (player.getId() == id)
 | 
			
		||||
                return player;
 | 
			
		||||
        LOGGER.log(Level.ERROR, "no player found with connection {0}", id); //NON-NLS
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a message to the specified player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param player the player to send the message to
 | 
			
		||||
     * @param msg    the message to send
 | 
			
		||||
     */
 | 
			
		||||
    void send(Player player, ServerMessage msg) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "sending to {0}: {1}", player, msg); //NON-NLS
 | 
			
		||||
        serverSender.send(player.getId(), msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a new player to the game if there are less than two players.
 | 
			
		||||
     * Transitions the state to SET_UP if two players are present.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id the connection ID of the new player
 | 
			
		||||
     * @return the player added to the game, or null if the game is not in the right state
 | 
			
		||||
     */
 | 
			
		||||
    public Player addPlayer(int id) {
 | 
			
		||||
        if (state != ServerState.WAIT) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "addPlayer not allowed in {0}", state); //NON-NLS
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        final int n = players.size() + 1;
 | 
			
		||||
        final Player player = new Player(id, "player " + n, config); //NON-NLS
 | 
			
		||||
        LOGGER.log(Level.INFO, "adding {0}", player); //NON-NLS
 | 
			
		||||
        players.add(player);
 | 
			
		||||
        if (players.size() == 2) {
 | 
			
		||||
            activePlayer = players.get(0);
 | 
			
		||||
            for (Player p : players)
 | 
			
		||||
                send(p, new GameDetails(config));
 | 
			
		||||
            setState(ServerState.SET_UP);
 | 
			
		||||
        }
 | 
			
		||||
        return player;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the reception of a MapMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the received MapMessage
 | 
			
		||||
     * @param from the ID of the sender client
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(MapMessage msg, int from) {
 | 
			
		||||
        if (state != ServerState.SET_UP)
 | 
			
		||||
            LOGGER.log(Level.ERROR, "playerReady not allowed in {0}", state); //NON-NLS
 | 
			
		||||
        else if (validMap(msg))
 | 
			
		||||
            playerReady(getPlayerById(from), msg.getShips());
 | 
			
		||||
        else {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "map does not fit game details"); //NON-NLS
 | 
			
		||||
            send(getPlayerById(from), new GameDetails(config));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validates the received map message by checking if all ships are within bounds
 | 
			
		||||
     * and do not overlap with each other.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the received MapMessage containing the ships
 | 
			
		||||
     * @return true if the map is valid, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean validMap(MapMessage msg) {
 | 
			
		||||
        List<Battleship> ships = msg.getShips();
 | 
			
		||||
        return inBounds(ships) && !overlaps(ships);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if all ships in the given list are within the bounds of the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ships the list of Battleships to validate
 | 
			
		||||
     * @return true if all ships are within bounds, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean inBounds(List<Battleship> ships) {
 | 
			
		||||
        for (Battleship ship : ships) {
 | 
			
		||||
            if (!isWithinBounds(ship)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the given ship is within the bounds of the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the Battleship to check
 | 
			
		||||
     * @return true if the ship is within bounds, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean isWithinBounds(Battleship ship) {
 | 
			
		||||
        int minX = ship.getMinX();
 | 
			
		||||
        int maxX = ship.getMaxX();
 | 
			
		||||
        int minY = ship.getMinY();
 | 
			
		||||
        int maxY = ship.getMaxY();
 | 
			
		||||
        int width = config.getMapWidth();
 | 
			
		||||
        int height = config.getMapHeight();
 | 
			
		||||
        return minX >= 0 && minX < width &&
 | 
			
		||||
                minY >= 0 && minY < height &&
 | 
			
		||||
                maxX >= 0 && maxX < width &&
 | 
			
		||||
                maxY >= 0 && maxY < height;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if any ships in the given list overlap with each other.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ships the list of Battleships to validate
 | 
			
		||||
     * @return true if any ships overlap, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean overlaps(List<Battleship> ships) {
 | 
			
		||||
        for (int i = 0; i < ships.size(); i++) {
 | 
			
		||||
            Battleship ship1 = ships.get(i);
 | 
			
		||||
            for (int j = i + 1; j < ships.size(); j++) {
 | 
			
		||||
                Battleship ship2 = ships.get(j);
 | 
			
		||||
                if (ship1.collidesWith(ship2)) {
 | 
			
		||||
                    return true; // Collision detected
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the reception of a ShootMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the received ShootMessage
 | 
			
		||||
     * @param from the ID of the sender client
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(ShootMessage msg, int from) {
 | 
			
		||||
        if (state != ServerState.BATTLE)
 | 
			
		||||
            LOGGER.log(Level.ERROR, "shoot not allowed in {0}", state); //NON-NLS
 | 
			
		||||
        else {
 | 
			
		||||
            setState(ServerState.ANIMATION);
 | 
			
		||||
            shoot(getPlayerById(from), msg.getPosition());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the reception of an {@link AnimationMessage}.
 | 
			
		||||
     * Marks the player's animation as finished and transitions the game state if necessary.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the received {@code AnimationMessage}
 | 
			
		||||
     * @param from the ID of the sender client
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(AnimationMessage msg, int from) {
 | 
			
		||||
        if (state != ServerState.ANIMATION)
 | 
			
		||||
            LOGGER.log(Level.ERROR, "animation not allowed in {0}", state); //NON-NLS
 | 
			
		||||
        else
 | 
			
		||||
            finishedAnimation(getPlayerById(from));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Marks the player's animation as finished and transitions the game state if necessary.
 | 
			
		||||
     *
 | 
			
		||||
     * @param player the player whose animation is finished
 | 
			
		||||
     */
 | 
			
		||||
    private void finishedAnimation(Player player) {
 | 
			
		||||
        if (!finishedAnimation.add(player)) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "{0}'s animation was already finished", player);
 | 
			
		||||
        }
 | 
			
		||||
        if (finishedAnimation.size() == 2) {
 | 
			
		||||
            finishedAnimation.clear();
 | 
			
		||||
            setState(ServerState.BATTLE);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Marks the player as ready and sets their ships.
 | 
			
		||||
     * Transitions the state to PLAY if both players are ready.
 | 
			
		||||
     *
 | 
			
		||||
     * @param player the player who is ready
 | 
			
		||||
     * @param ships  the list of ships placed by the player
 | 
			
		||||
     */
 | 
			
		||||
    void playerReady(Player player, List<Battleship> ships) {
 | 
			
		||||
        if (!readyPlayers.add(player)) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "{0} was already ready", player); //NON-NLS
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        ships.forEach(player.getMap()::add);
 | 
			
		||||
        if (readyPlayers.size() == 2) {
 | 
			
		||||
            for (Player p : players)
 | 
			
		||||
                send(p, new StartBattleMessage(p == activePlayer));
 | 
			
		||||
            setState(ServerState.BATTLE);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the shooting action by the player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param p   the player who shot
 | 
			
		||||
     * @param pos the position of the shot
 | 
			
		||||
     */
 | 
			
		||||
    void shoot(Player p, IntPoint pos) {
 | 
			
		||||
        if (p != activePlayer) return;
 | 
			
		||||
        final Player otherPlayer = getOpponent(activePlayer);
 | 
			
		||||
        final Battleship selectedShip = otherPlayer.getMap().findShipAt(pos);
 | 
			
		||||
        if (selectedShip == null) {
 | 
			
		||||
            // shot missed
 | 
			
		||||
            send(activePlayer, EffectMessage.miss(true, pos));
 | 
			
		||||
            send(otherPlayer, EffectMessage.miss(false, pos));
 | 
			
		||||
            activePlayer = otherPlayer;
 | 
			
		||||
        } else {
 | 
			
		||||
            // shot hit a ship
 | 
			
		||||
            selectedShip.hit(pos);
 | 
			
		||||
            if (otherPlayer.getMap().getRemainingShips().isEmpty()) {
 | 
			
		||||
                // game is over
 | 
			
		||||
                send(activePlayer, EffectMessage.won(pos, selectedShip));
 | 
			
		||||
                send(otherPlayer, EffectMessage.lost(pos, selectedShip, activePlayer.getMap().getRemainingShips()));
 | 
			
		||||
                setState(ServerState.GAME_OVER);
 | 
			
		||||
            } else if (selectedShip.isDestroyed()) {
 | 
			
		||||
                // ship has been destroyed, but game is not yet over
 | 
			
		||||
                send(activePlayer, EffectMessage.shipDestroyed(true, pos, selectedShip));
 | 
			
		||||
                send(otherPlayer, EffectMessage.shipDestroyed(false, pos, selectedShip));
 | 
			
		||||
            } else {
 | 
			
		||||
                // ship has been hit, but it hasn't been destroyed
 | 
			
		||||
                send(activePlayer, EffectMessage.hit(true, pos));
 | 
			
		||||
                send(otherPlayer, EffectMessage.hit(false, pos));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||