Compare commits
	
		
			No commits in common. "main" and "add_experience_points" have entirely different histories.
		
	
	
		
			main
			...
			add_experi
		
	
		
| @ -1,16 +0,0 @@ | ||||
| name: publish package | ||||
| on: | ||||
|   push: | ||||
|     branches-ignore: | ||||
|       - main | ||||
| 
 | ||||
| jobs: | ||||
|   Explore-Gitea-Actions: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out repository code | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: run tests | ||||
|         run: | | ||||
|           cd ${{ gitea.workspace }} | ||||
|           cargo test | ||||
| @ -1,28 +0,0 @@ | ||||
| name: publish package | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - main   | ||||
| 
 | ||||
| jobs: | ||||
|   Explore-Gitea-Actions: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out repository code | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: run tests | ||||
|         run: | | ||||
|           cd ${{ gitea.workspace }} | ||||
|           cargo test --release                     | ||||
|       - name: build & release package | ||||
|         run: | | ||||
|           cd ${{ gitea.workspace }} | ||||
|           cargo build --release | ||||
|           cargo deb | ||||
|           ls -al target/debian | ||||
|           UPLOAD_FILE=$(ls target/debian/*.deb) | ||||
|           curl --user jlusiardi:${{ secrets.UPLOAD_TOKEN }} --upload-file ${UPLOAD_FILE} https://gitea.intern.haus-in-hirschaid.de/api/packages/jlusiardi/debian/pool/bionic/main/upload           | ||||
|           echo -e "${{ secrets.DEPLOY_PRIVATE_KEY }}" > /tmp/ssh_key | ||||
|           chmod 0600 /tmp/ssh_key | ||||
|           scp -o StrictHostKeyChecking=no -i /tmp/ssh_key ${UPLOAD_FILE} jlusiardi@april2023.lusiardi.de:/var/www/p17.lusiardi.de/el_diabolo | ||||
|           ssh -o StrictHostKeyChecking=no -i /tmp/ssh_key jlusiardi@april2023.lusiardi.de /var/www/p17.lusiardi.de/el_diabolo/generate_index.sh | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +1 @@ | ||||
| /target | ||||
| *.profraw | ||||
|  | ||||
							
								
								
									
										347
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										347
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -50,46 +50,23 @@ version = "0.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "castaway" | ||||
| version = "0.2.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" | ||||
| dependencies = [ | ||||
|  "rustversion", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "cfg-if" | ||||
| version = "1.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "compact_str" | ||||
| version = "0.8.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" | ||||
| dependencies = [ | ||||
|  "castaway", | ||||
|  "cfg-if", | ||||
|  "itoa", | ||||
|  "rustversion", | ||||
|  "ryu", | ||||
|  "static_assertions", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "crossterm" | ||||
| version = "0.28.1" | ||||
| version = "0.27.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" | ||||
| checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" | ||||
| dependencies = [ | ||||
|  "bitflags 2.4.1", | ||||
|  "crossterm_winapi", | ||||
|  "libc", | ||||
|  "mio", | ||||
|  "parking_lot", | ||||
|  "rustix", | ||||
|  "signal-hook", | ||||
|  "signal-hook-mio", | ||||
|  "winapi", | ||||
| @ -104,41 +81,6 @@ dependencies = [ | ||||
|  "winapi", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "darling" | ||||
| version = "0.20.10" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" | ||||
| dependencies = [ | ||||
|  "darling_core", | ||||
|  "darling_macro", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "darling_core" | ||||
| version = "0.20.10" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" | ||||
| dependencies = [ | ||||
|  "fnv", | ||||
|  "ident_case", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "strsim", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "darling_macro" | ||||
| version = "0.20.10" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" | ||||
| dependencies = [ | ||||
|  "darling_core", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "either" | ||||
| version = "1.9.0" | ||||
| @ -147,10 +89,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "el_diabolo" | ||||
| version = "0.2.3" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "crossterm", | ||||
|  "macros", | ||||
|  "petgraph", | ||||
|  "rand", | ||||
|  "ratatui", | ||||
| @ -163,28 +104,12 @@ version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "errno" | ||||
| version = "0.3.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "windows-sys", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "fixedbitset" | ||||
| version = "0.4.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "fnv" | ||||
| version = "1.0.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "getrandom" | ||||
| version = "0.2.11" | ||||
| @ -208,21 +133,9 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "heck" | ||||
| version = "0.5.0" | ||||
| version = "0.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "hermit-abi" | ||||
| version = "0.3.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "ident_case" | ||||
| version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" | ||||
| checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "indexmap" | ||||
| @ -236,35 +149,19 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "indoc" | ||||
| version = "2.0.5" | ||||
| version = "2.0.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "instability" | ||||
| version = "0.3.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" | ||||
| dependencies = [ | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "itertools" | ||||
| version = "0.13.0" | ||||
| version = "0.11.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" | ||||
| checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" | ||||
| dependencies = [ | ||||
|  "either", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "itoa" | ||||
| version = "1.0.11" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "js-sys" | ||||
| version = "0.3.66" | ||||
| @ -276,15 +173,9 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "libc" | ||||
| version = "0.2.161" | ||||
| version = "0.2.149" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "linux-raw-sys" | ||||
| version = "0.4.14" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" | ||||
| checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "lock_api" | ||||
| @ -311,23 +202,12 @@ dependencies = [ | ||||
|  "hashbrown", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "macros" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "darling", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "mio" | ||||
| version = "1.0.2" | ||||
| version = "0.8.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" | ||||
| checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" | ||||
| dependencies = [ | ||||
|  "hermit-abi", | ||||
|  "libc", | ||||
|  "log", | ||||
|  "wasi", | ||||
| @ -358,9 +238,9 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "libc", | ||||
|  "redox_syscall 0.4.1", | ||||
|  "redox_syscall", | ||||
|  "smallvec", | ||||
|  "windows-targets 0.48.5", | ||||
|  "windows-targets", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -371,9 +251,9 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "petgraph" | ||||
| version = "0.6.5" | ||||
| version = "0.6.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" | ||||
| checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" | ||||
| dependencies = [ | ||||
|  "fixedbitset", | ||||
|  "indexmap", | ||||
| @ -387,18 +267,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "proc-macro2" | ||||
| version = "1.0.89" | ||||
| version = "1.0.69" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" | ||||
| checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" | ||||
| dependencies = [ | ||||
|  "unicode-ident", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "quote" | ||||
| version = "1.0.37" | ||||
| version = "1.0.33" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" | ||||
| checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
| ] | ||||
| @ -435,23 +315,20 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "ratatui" | ||||
| version = "0.29.0" | ||||
| version = "0.24.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" | ||||
| checksum = "0ebc917cfb527a566c37ecb94c7e3fd098353516fb4eb6bea17015ade0182425" | ||||
| dependencies = [ | ||||
|  "bitflags 2.4.1", | ||||
|  "cassowary", | ||||
|  "compact_str", | ||||
|  "crossterm", | ||||
|  "indoc", | ||||
|  "instability", | ||||
|  "itertools", | ||||
|  "lru", | ||||
|  "paste", | ||||
|  "strum", | ||||
|  "unicode-segmentation", | ||||
|  "unicode-truncate", | ||||
|  "unicode-width 0.2.0", | ||||
|  "unicode-width", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -463,40 +340,12 @@ dependencies = [ | ||||
|  "bitflags 1.3.2", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "redox_syscall" | ||||
| version = "0.5.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" | ||||
| dependencies = [ | ||||
|  "bitflags 2.4.1", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rustix" | ||||
| version = "0.38.37" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" | ||||
| dependencies = [ | ||||
|  "bitflags 2.4.1", | ||||
|  "errno", | ||||
|  "libc", | ||||
|  "linux-raw-sys", | ||||
|  "windows-sys", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rustversion" | ||||
| version = "1.0.14" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "ryu" | ||||
| version = "1.0.18" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "scopeguard" | ||||
| version = "1.2.0" | ||||
| @ -515,9 +364,9 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "signal-hook-mio" | ||||
| version = "0.2.4" | ||||
| version = "0.2.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" | ||||
| checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "mio", | ||||
| @ -539,32 +388,20 @@ version = "1.11.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "static_assertions" | ||||
| version = "1.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "strsim" | ||||
| version = "0.11.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "strum" | ||||
| version = "0.26.3" | ||||
| version = "0.25.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" | ||||
| checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" | ||||
| dependencies = [ | ||||
|  "strum_macros", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "strum_macros" | ||||
| version = "0.26.4" | ||||
| version = "0.25.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" | ||||
| checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" | ||||
| dependencies = [ | ||||
|  "heck", | ||||
|  "proc-macro2", | ||||
| @ -575,9 +412,9 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "2.0.85" | ||||
| version = "2.0.38" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" | ||||
| checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
| @ -596,28 +433,11 @@ version = "1.10.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unicode-truncate" | ||||
| version = "1.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" | ||||
| dependencies = [ | ||||
|  "itertools", | ||||
|  "unicode-segmentation", | ||||
|  "unicode-width 0.1.14", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unicode-width" | ||||
| version = "0.1.14" | ||||
| version = "0.1.11" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unicode-width" | ||||
| version = "0.2.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" | ||||
| checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "version_check" | ||||
| @ -631,12 +451,6 @@ version = "0.11.0+wasi-snapshot-preview1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "wasite" | ||||
| version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "wasm-bindgen" | ||||
| version = "0.2.89" | ||||
| @ -703,12 +517,11 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "whoami" | ||||
| version = "1.5.2" | ||||
| version = "1.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" | ||||
| checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" | ||||
| dependencies = [ | ||||
|  "redox_syscall 0.5.7", | ||||
|  "wasite", | ||||
|  "wasm-bindgen", | ||||
|  "web-sys", | ||||
| ] | ||||
| 
 | ||||
| @ -736,11 +549,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows-sys" | ||||
| version = "0.52.0" | ||||
| version = "0.48.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" | ||||
| checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" | ||||
| dependencies = [ | ||||
|  "windows-targets 0.52.6", | ||||
|  "windows-targets", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -749,29 +562,13 @@ version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" | ||||
| dependencies = [ | ||||
|  "windows_aarch64_gnullvm 0.48.5", | ||||
|  "windows_aarch64_msvc 0.48.5", | ||||
|  "windows_i686_gnu 0.48.5", | ||||
|  "windows_i686_msvc 0.48.5", | ||||
|  "windows_x86_64_gnu 0.48.5", | ||||
|  "windows_x86_64_gnullvm 0.48.5", | ||||
|  "windows_x86_64_msvc 0.48.5", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows-targets" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" | ||||
| dependencies = [ | ||||
|  "windows_aarch64_gnullvm 0.52.6", | ||||
|  "windows_aarch64_msvc 0.52.6", | ||||
|  "windows_i686_gnu 0.52.6", | ||||
|  "windows_i686_gnullvm", | ||||
|  "windows_i686_msvc 0.52.6", | ||||
|  "windows_x86_64_gnu 0.52.6", | ||||
|  "windows_x86_64_gnullvm 0.52.6", | ||||
|  "windows_x86_64_msvc 0.52.6", | ||||
|  "windows_aarch64_gnullvm", | ||||
|  "windows_aarch64_msvc", | ||||
|  "windows_i686_gnu", | ||||
|  "windows_i686_msvc", | ||||
|  "windows_x86_64_gnu", | ||||
|  "windows_x86_64_gnullvm", | ||||
|  "windows_x86_64_msvc", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -780,90 +577,42 @@ version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_aarch64_gnullvm" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_aarch64_msvc" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_aarch64_msvc" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_i686_gnu" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_i686_gnu" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_i686_gnullvm" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_i686_msvc" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_i686_msvc" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnu" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnu" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnullvm" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_x86_64_gnullvm" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_x86_64_msvc" | ||||
| version = "0.48.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "windows_x86_64_msvc" | ||||
| version = "0.52.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "zerocopy" | ||||
| version = "0.7.19" | ||||
|  | ||||
							
								
								
									
										25
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								Cargo.toml
									
									
									
									
									
								
							| @ -1,28 +1,13 @@ | ||||
| [workspace] | ||||
| members = ["macros"] | ||||
| 
 | ||||
| [package] | ||||
| name = "el_diabolo" | ||||
| version = "0.2.3" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
| 
 | ||||
| [dependencies] | ||||
| ratatui = "0.29.0" | ||||
| crossterm = "0.28.1" | ||||
| ratatui = "0.24.0" | ||||
| crossterm = "0.27.0" | ||||
| rand = "0.8.5" | ||||
| petgraph = "0.6.5" | ||||
| whoami = "1.5.2" | ||||
| macros = { path = "./macros" } | ||||
| 
 | ||||
| [package.metadata.deb] | ||||
| maintainer = "Joachim Lusiardi <joachim@lusiardi.de>" | ||||
| copyright = "2024, Joachim Lusiardi <joachim@lusiardi.de>" | ||||
| extended-description = """Little roguelike game""" | ||||
| depends = "$auto" | ||||
| section = "game" | ||||
| priority = "optional" | ||||
| assets = [ | ||||
|     ["target/release/el_diabolo", "/usr/bin/", "755"], | ||||
| ] | ||||
| petgraph = "0.6.4" | ||||
| whoami = "1.4.1" | ||||
							
								
								
									
										10
									
								
								build.rs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								build.rs
									
									
									
									
									
								
							| @ -1,10 +0,0 @@ | ||||
| use std::process::Command; | ||||
| 
 | ||||
| fn main() { | ||||
|     let output = Command::new("git") | ||||
|         .args(["rev-parse", "--short", "HEAD"]) | ||||
|         .output() | ||||
|         .unwrap(); | ||||
|     let git_hash = String::from_utf8(output.stdout).unwrap(); | ||||
|     println!("cargo:rustc-env=GIT_HASH={}", git_hash); | ||||
| } | ||||
							
								
								
									
										10
									
								
								coverage.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								coverage.md
									
									
									
									
									
								
							| @ -1,10 +0,0 @@ | ||||
| ``` | ||||
| cargo install grcov | ||||
| rustup component add llvm-tools-preview | ||||
| ``` | ||||
| 
 | ||||
| ``` | ||||
| RUSTFLAGS="-Cinstrument-coverage" cargo clean | ||||
| RUSTFLAGS="-Cinstrument-coverage" cargo test | ||||
| grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html | ||||
| ``` | ||||
| @ -1,17 +0,0 @@ | ||||
| [package] | ||||
| name = "macros" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
| [lib] | ||||
| name = "macros" | ||||
| path = "src/lib.rs" | ||||
| proc-macro = true | ||||
| 
 | ||||
| 
 | ||||
| [dependencies] | ||||
| syn = "2.0.85" | ||||
| quote = "1.0.37" | ||||
| proc-macro2 = "1.0.89" | ||||
| darling= "0.20.10" | ||||
| @ -1,58 +0,0 @@ | ||||
| extern crate proc_macro; | ||||
| 
 | ||||
| use proc_macro::TokenStream; | ||||
| use quote::quote; | ||||
| 
 | ||||
| #[proc_macro_derive(CreateMonsters)] | ||||
| pub fn create_monsters(input: TokenStream) -> TokenStream { | ||||
|     let input = syn::parse_macro_input!(input as syn::File); | ||||
|     let (enum_item, _) = match &input.items[0] { | ||||
|         syn::Item::Enum(enum_item) => (enum_item, &enum_item.attrs), | ||||
|         _ => panic!("must be an enum"), | ||||
|     }; | ||||
| 
 | ||||
|     let structs = enum_item.variants.iter().map(|variant| { | ||||
|         let variant_name = &variant.ident; | ||||
| 
 | ||||
|         let t = quote! { | ||||
|         pub struct #variant_name { | ||||
|             name: String, | ||||
|             life: usize, | ||||
|             position: Position, | ||||
|             symbol: String, | ||||
|             color: Color, | ||||
|             experience_gain: usize, | ||||
|             ticks_between_steps: u128, | ||||
|             damage_range: RangeInclusive<usize>, | ||||
|             defense: usize, | ||||
|         } | ||||
|         impl Monster for #variant_name { | ||||
|             fn get_name(&self) -> &str { &self.name } | ||||
|             fn is_dead(&self) -> bool { self.life <= 0 } | ||||
|             fn get_experience_gain(&self) -> usize { self.experience_gain } | ||||
|             fn get_representation(&self) -> (&str, Color) { (&self.symbol, self.color) } | ||||
|             fn decrease_life(&mut self, by: usize) { | ||||
|                 self.life = self.life.saturating_sub(by); | ||||
|             } | ||||
|             fn get_ticks_between_steps(&self) -> u128 { self.ticks_between_steps } | ||||
|             fn damage(&self) -> usize { rand::thread_rng().gen_range(self.damage_range.clone()) } | ||||
|             fn defense(&self) -> usize { self.defense } | ||||
|             #[cfg(test)] | ||||
|             fn get_life(&self) -> usize { self.life } | ||||
|         } | ||||
|         impl HasPosition for #variant_name { | ||||
|             fn get_position(&mut self) -> &mut Position { | ||||
|                 &mut self.position | ||||
|             } | ||||
|             fn get_immutable_position(&self) -> &Position { | ||||
|                 &self.position | ||||
|             } | ||||
|         } | ||||
|         }; | ||||
|         t | ||||
|     }); | ||||
|     let output = quote::quote! { | ||||
|         #(#structs)* | ||||
|     }; | ||||
|     output.into() | ||||
| } | ||||
| @ -1,4 +1,3 @@ | ||||
| use rand::Rng; | ||||
| use ratatui::style::Color; | ||||
| 
 | ||||
| use crate::player::Player; | ||||
| @ -6,51 +5,40 @@ use crate::position::Position; | ||||
| 
 | ||||
| pub trait Artifact { | ||||
|     //! An artifact that can be collected by the player
 | ||||
|     /// get the character and color used to draw the artifact into the level
 | ||||
|     fn get_representation(&self) -> (&str, Color); | ||||
|     /// get the position of the artifact in the level
 | ||||
|     fn get_immutable_position(&self) -> &Position; | ||||
|     /// call to apply the effects of the artifact to the player
 | ||||
|     fn collect(&mut self, player: &mut Player, messages: &mut Vec<String>); | ||||
|     fn collect(&mut self, player: &mut Player); | ||||
|     /// returns if the artifact was collected and can be removed from the level
 | ||||
|     fn was_collected(&self) -> bool; | ||||
| } | ||||
| 
 | ||||
| /// An artifact that contains a random amount of gold pieces.
 | ||||
| pub struct Chest { | ||||
|     /// the chests position
 | ||||
|     /// a chest that contains some gold
 | ||||
|     position: Position, | ||||
|     /// the chests value
 | ||||
|     gold: usize, | ||||
| } | ||||
| 
 | ||||
| impl Chest { | ||||
|     /// create a chest at the given position with a random amount of gold.
 | ||||
|     /// The gold amount depends on the level, the deeper you go, the more you get.
 | ||||
|     pub fn new(position: Position) -> Self { | ||||
|         let min_gold = 10 * (position.get_level() + 1); | ||||
|         let max_gold = min_gold + 10 * position.get_level(); | ||||
|         let level = position.get_level(); | ||||
|         Self { | ||||
|             position, | ||||
|             gold: rand::thread_rng().gen_range(min_gold..=max_gold), | ||||
|             // TODO maybe randomize this?
 | ||||
|             gold: (level + 1) * 10, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Artifact for Chest { | ||||
|     fn get_representation(&self) -> (&str, Color) { | ||||
|         ("C", Color::Cyan) | ||||
|         ("C", Color::Blue) | ||||
|     } | ||||
| 
 | ||||
|     fn get_immutable_position(&self) -> &Position { | ||||
|         &self.position | ||||
|     } | ||||
|     fn collect(&mut self, player: &mut Player, messages: &mut Vec<String>) { | ||||
|     fn get_immutable_position(&self) -> &Position { &self.position } | ||||
|     fn collect(&mut self, player: &mut Player) { | ||||
|         player.retrieve_gold(self.gold); | ||||
|         messages.insert( | ||||
|             0, | ||||
|             format!("opened chest and collected {} gold.", self.gold).to_string(), | ||||
|         ); | ||||
|         self.gold = 0; | ||||
|     } | ||||
| 
 | ||||
| @ -59,64 +47,32 @@ impl Artifact for Chest { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Copy)] | ||||
| /// An artifact that gives the player some health on consumption.
 | ||||
| pub struct Potion { | ||||
|     /// a potion that restores some health
 | ||||
|     position: Position, | ||||
|     health: usize, | ||||
|     was_collected: bool, | ||||
| } | ||||
| 
 | ||||
| impl Potion { | ||||
|     pub fn new(position: Position) -> Self { | ||||
|         let min_health_gain = 5 + position.get_level(); | ||||
|         let max_health_gain = min_health_gain + 3 * position.get_level(); | ||||
|         Self { | ||||
|             position, | ||||
|             health: rand::thread_rng().gen_range(min_health_gain..=max_health_gain), | ||||
|             was_collected: false, | ||||
|             health: 5, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_health(&self) -> usize { | ||||
|         self.health | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Artifact for Potion { | ||||
|     fn get_representation(&self) -> (&str, Color) { | ||||
|         ("P", Color::Green) | ||||
|     } | ||||
| 
 | ||||
|     fn get_immutable_position(&self) -> &Position { | ||||
|         &self.position | ||||
|     } | ||||
| 
 | ||||
|     fn collect(&mut self, player: &mut Player, messages: &mut Vec<String>) { | ||||
|         //! called when the player walked on to a potion.
 | ||||
|         //!
 | ||||
|         //! Depending on health status and inventory usage the potion will
 | ||||
|         //! be consumed directly or moved to inventory.
 | ||||
|         if !player.is_healthy() { | ||||
|             let old = player.get_life(); | ||||
|             player.change_life(self.health.try_into().unwrap()); | ||||
|             let new = player.get_life(); | ||||
|             messages.insert( | ||||
|                 0, | ||||
|                 format!("picked up potion and gained {} health.", new - old).to_string(), | ||||
|             ); | ||||
|             self.health = 0; | ||||
|             self.was_collected = true; | ||||
|         } else if player.add_to_inventory(self) { | ||||
|             messages.insert(0, "move potion to inventory.".to_string()); | ||||
|             self.was_collected = true; | ||||
|         } else { | ||||
|             messages.insert(0, "inventory is full.".to_string()); | ||||
|         } | ||||
|     fn get_immutable_position(&self) -> &Position { &self.position } | ||||
|     fn collect(&mut self, player: &mut Player) { | ||||
|         player.change_life(self.health.try_into().unwrap()); | ||||
|         self.health = 0; | ||||
|     } | ||||
| 
 | ||||
|     fn was_collected(&self) -> bool { | ||||
|         self.was_collected | ||||
|         self.health == 0 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										137
									
								
								src/constants.rs
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								src/constants.rs
									
									
									
									
									
								
							| @ -1,137 +0,0 @@ | ||||
| use std::{collections::HashMap, ops::RangeInclusive}; | ||||
| 
 | ||||
| use crate::{monsters::MonsterTypes, room::RoomType}; | ||||
| 
 | ||||
| /// the number of rooms in vertical direction
 | ||||
| pub const ROOMS_HORIZONTAL: usize = 8; | ||||
| /// the number of rooms in horizontal direction
 | ||||
| pub const ROOMS_VERTICAL: usize = 7; | ||||
| 
 | ||||
| /// the width of a room in the grid of rooms (number of characters)
 | ||||
| pub const ROOM_WIDTH: usize = 9; | ||||
| /// the height of a room in the grid of rooms (number of characters)
 | ||||
| pub const ROOM_HEIGHT: usize = 6; | ||||
| 
 | ||||
| // the minmal width of a room
 | ||||
| pub const ROOM_MIN_WIDTH: usize = 4; | ||||
| 
 | ||||
| // the minmal height of a room
 | ||||
| pub const ROOM_MIN_HEIGHT: usize = 4; | ||||
| 
 | ||||
| /// How many levels does the dungeon have?
 | ||||
| pub const LEVELS: usize = 4; | ||||
| 
 | ||||
| /// length of a game frame in ms
 | ||||
| pub const FRAME_LENGTH: u64 = 100; | ||||
| 
 | ||||
| /// define the minimum width of a terminal to run the game, must be at least the level width plus some space for stats and messages
 | ||||
| pub const MIN_WIDTH: u16 = 120; | ||||
| /// define the minimum height of a terminal to run the game, this must be at least the level height!
 | ||||
| pub const MIN_HEIGHT: u16 = LEVEL_HEIGHT as u16; | ||||
| 
 | ||||
| /// the calculated width of a level
 | ||||
| pub const LEVEL_WIDTH: usize = 1 + ROOMS_HORIZONTAL * ROOM_WIDTH; | ||||
| /// the calculated height of a level
 | ||||
| pub const LEVEL_HEIGHT: usize = 1 + ROOMS_VERTICAL * ROOM_HEIGHT; | ||||
| 
 | ||||
| pub fn get_monsters_per_level() -> Vec<HashMap<MonsterTypes, std::ops::RangeInclusive<u8>>> { | ||||
|     let tmp = [ | ||||
|         // level 1
 | ||||
|         vec![(MonsterTypes::Rat, 50), (MonsterTypes::Spider, 50)], | ||||
|         // level 2
 | ||||
|         vec![(MonsterTypes::Rat, 50), (MonsterTypes::Snake, 50)], | ||||
|         // level 3
 | ||||
|         vec![ | ||||
|             (MonsterTypes::Orc, 34), | ||||
|             (MonsterTypes::Skeleton, 33), | ||||
|             (MonsterTypes::Snake, 33), | ||||
|         ], | ||||
|         // level 4
 | ||||
|         vec![ | ||||
|             (MonsterTypes::Orc, 34), | ||||
|             (MonsterTypes::Skeleton, 33), | ||||
|             (MonsterTypes::Snake, 33), | ||||
|         ], | ||||
|     ]; | ||||
|     if tmp.len() < LEVELS { | ||||
|         panic!( | ||||
|             "Only {} monster sets defined for {} levels!", | ||||
|             tmp.len(), | ||||
|             LEVELS | ||||
|         ); | ||||
|     } | ||||
|     let mut result: Vec<HashMap<MonsterTypes, std::ops::RangeInclusive<u8>>> = vec![]; | ||||
|     for (idx, level) in tmp.iter().enumerate() { | ||||
|         let mut sum = 0; | ||||
|         let mut map: HashMap<MonsterTypes, RangeInclusive<u8>> = HashMap::new(); | ||||
|         for monster in level { | ||||
|             map.insert(monster.0, RangeInclusive::new(sum + 1, sum + monster.1)); | ||||
|             sum += monster.1; | ||||
|         } | ||||
|         if sum != 100 { | ||||
|             panic!( | ||||
|                 "all percentages must add to 100 (was {}) per level, error in level {}!", | ||||
|                 sum, | ||||
|                 idx + 1 | ||||
|             ); | ||||
|         } | ||||
|         result.push(map); | ||||
|     } | ||||
|     result | ||||
| } | ||||
| 
 | ||||
| pub fn get_room_type_per_level() -> Vec<HashMap<RoomType, std::ops::RangeInclusive<u8>>> { | ||||
|     let tmp = [ | ||||
|         // level 1
 | ||||
|         vec![ | ||||
|             (RoomType::EmptyRoom, 50), | ||||
|             (RoomType::ArtifactRoom, 10), | ||||
|             (RoomType::MonsterRoom, 20), | ||||
|             (RoomType::BasicRoom, 20), | ||||
|         ], | ||||
|         // level 2
 | ||||
|         vec![ | ||||
|             (RoomType::EmptyRoom, 50), | ||||
|             (RoomType::BasicRoom, 25), | ||||
|             (RoomType::MonsterRoom, 12), | ||||
|             (RoomType::ArtifactRoom, 13), | ||||
|         ], | ||||
|         // level 3
 | ||||
|         vec![ | ||||
|             (RoomType::EmptyRoom, 50), | ||||
|             (RoomType::BasicRoom, 25), | ||||
|             (RoomType::MonsterRoom, 25), | ||||
|         ], | ||||
|         // level 4
 | ||||
|         vec![ | ||||
|             (RoomType::BasicRoom, 33), | ||||
|             (RoomType::MonsterRoom, 33), | ||||
|             (RoomType::ArtifactRoom, 34), | ||||
|         ], | ||||
|     ]; | ||||
|     if tmp.len() < LEVELS { | ||||
|         panic!( | ||||
|             "Only {} room sets defined for {} levels!", | ||||
|             tmp.len(), | ||||
|             LEVELS | ||||
|         ); | ||||
|     } | ||||
|     let mut result: Vec<HashMap<RoomType, std::ops::RangeInclusive<u8>>> = vec![]; | ||||
|     for (idx, level) in tmp.iter().enumerate() { | ||||
|         let mut sum = 0; | ||||
|         let mut map: HashMap<RoomType, RangeInclusive<u8>> = HashMap::new(); | ||||
|         for room in level { | ||||
|             map.insert(room.0, RangeInclusive::new(sum + 1, sum + room.1)); | ||||
|             sum += room.1; | ||||
|         } | ||||
|         if sum != 100 { | ||||
|             panic!( | ||||
|                 "all percentages must add to 100 (was {}) per level, error in level {}!", | ||||
|                 sum, | ||||
|                 idx + 1 | ||||
|             ); | ||||
|         } | ||||
|         result.push(map); | ||||
|     } | ||||
|     result | ||||
| } | ||||
							
								
								
									
										153
									
								
								src/game.rs
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								src/game.rs
									
									
									
									
									
								
							| @ -1,28 +1,28 @@ | ||||
| use crate::constants::LEVELS; | ||||
| use crate::level::{Level, StructureElement}; | ||||
| use crate::level_generator::LevelGenerator; | ||||
| use crate::player::Player; | ||||
| use crate::position::{HasPosition, Position}; | ||||
| use crate::position::Position; | ||||
| 
 | ||||
| pub const LEVELS: usize = 10; | ||||
| 
 | ||||
| #[derive(PartialEq)] | ||||
| /// represents a state of a game
 | ||||
| pub enum GameState { | ||||
|     /// the game is ongoing (neither won or lost)
 | ||||
|     Running, | ||||
|     RUNNING, | ||||
|     /// the player died
 | ||||
|     Lost, | ||||
|     LOST, | ||||
|     /// the player reached the Ω
 | ||||
|     Won, | ||||
|     WON, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /// the main structure to hold all information about the ongoing game
 | ||||
| pub struct Game { | ||||
|     /// the player
 | ||||
|     player: Player, | ||||
|     /// the levels of the game
 | ||||
|     levels: Vec<Level>, | ||||
|     /// messages that are displayed in the ui
 | ||||
|     pub messages: Vec<String>, | ||||
| } | ||||
| 
 | ||||
| impl Game { | ||||
| @ -34,24 +34,25 @@ impl Game { | ||||
|         let mut g = Game { | ||||
|             player: p, | ||||
|             levels: v, | ||||
|             messages: Vec::with_capacity(10), | ||||
|         }; | ||||
|         let start = { g.get_level(0).start }; | ||||
|         g.get_mutable_player() | ||||
|             .get_position() | ||||
|             .set(0, start.0, start.1); | ||||
|         g.get_mutable_level(0) | ||||
|             .discover(&Position::new(0, start.0, start.1)); | ||||
|         let start = { | ||||
|             g.get_level(0).start | ||||
|         }; | ||||
|         g.get_mutable_player().get_position().set(0, start.0, start.1); | ||||
|         g.get_mutable_level(0).discover(&Position::new(0, start.0, start.1)); | ||||
|         g | ||||
|     } | ||||
| 
 | ||||
|     /// returns true if the player is dead (life <= 0)
 | ||||
|     fn player_is_dead(&self) -> bool { self.get_player().get_life() <= 0 } | ||||
|     /// returns true if the player is standing on the End element
 | ||||
|     fn player_reached_goal(&mut self) -> bool { | ||||
|         match self.next_element(0, 0) { | ||||
|             None => {} | ||||
|             Some(a) => { | ||||
|                 if a == StructureElement::End { | ||||
|                     return true; | ||||
|                 match a { | ||||
|                     StructureElement::End => { return true; } | ||||
|                     _ => {} | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
| @ -59,13 +60,13 @@ impl Game { | ||||
|     } | ||||
|     /// returns the state of the game (depending on player's life and position)
 | ||||
|     pub fn get_game_state(&mut self) -> GameState { | ||||
|         if self.player.is_dead() { | ||||
|             return GameState::Lost; | ||||
|         if self.player_is_dead() { | ||||
|             return GameState::LOST; | ||||
|         } | ||||
|         if self.player_reached_goal() { | ||||
|             return GameState::Won; | ||||
|             return GameState::WON; | ||||
|         } | ||||
|         GameState::Running | ||||
|         GameState::RUNNING | ||||
|     } | ||||
|     pub fn get_player(&self) -> &Player { | ||||
|         &self.player | ||||
| @ -73,18 +74,25 @@ impl Game { | ||||
|     pub fn get_mutable_player(&mut self) -> &mut Player { | ||||
|         &mut self.player | ||||
|     } | ||||
|     pub fn get_level(&self, level: usize) -> &Level { | ||||
|         &self.levels[level] | ||||
|     } | ||||
|     pub fn get_mutable_level(&mut self, level: usize) -> &mut Level { | ||||
|         &mut self.levels[level] | ||||
|     } | ||||
|     pub fn get_level(&self, level: usize) -> &Level { &self.levels[level] } | ||||
|     pub fn get_mutable_level(&mut self, level: usize) -> &mut Level { &mut self.levels[level] } | ||||
|     /// check if the player can move in the given direction. This only checks for structure element
 | ||||
|     /// limitation as walls.
 | ||||
|     fn can_move(&mut self, dx: i16, dy: i16) -> bool { | ||||
|         let player_pos = &self.player.get_position(); | ||||
|         let new_x: i16 = player_pos.get_x() as i16 + dx; | ||||
|         let new_y: i16 = player_pos.get_y() as i16 + dy; | ||||
|         let level = &mut self.levels[player_pos.get_level()]; | ||||
|         level.can_player_move(&self.player, dx, dy) | ||||
|         match level.get_element(new_x, new_y) { | ||||
|             (None, _, _) => { return false; } | ||||
|             (Some(t), _, _) => { | ||||
|                 match t { | ||||
|                     StructureElement::Wall => { return false; } | ||||
|                     _ => {} | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|         true | ||||
|     } | ||||
|     /// returns the position (as tuple) of the next level's start point.
 | ||||
|     fn next_start(&self) -> (usize, usize, usize) { | ||||
| @ -123,39 +131,32 @@ impl Game { | ||||
|         let structure = self.next_element(dx, dy); | ||||
| 
 | ||||
|         // use stairs if walked onto one
 | ||||
|         if let Some(e) = structure { | ||||
|             match e { | ||||
|                 StructureElement::StairDown => { | ||||
|                     (dx, dy) = (0, 0); | ||||
|                     let (next_level, x, y) = self.next_start(); | ||||
|                     player_level = next_level; | ||||
|                     self.messages | ||||
|                         .insert(0, format!("you climb down to level {}.", next_level + 1)); | ||||
|                     self.get_mutable_player() | ||||
|                         .get_position() | ||||
|                         .set(next_level, x, y); | ||||
|         match structure { | ||||
|             Some(e) => { | ||||
|                 match e { | ||||
|                     StructureElement::StairDown => { | ||||
|                         (dx, dy) = (0, 0); | ||||
|                         let (next_level, x, y) = self.next_start(); | ||||
|                         player_level = next_level; | ||||
|                         self.get_mutable_player().get_position().set(next_level, x, y); | ||||
|                     } | ||||
|                     StructureElement::StairUp => { | ||||
|                         (dx, dy) = (0, 0); | ||||
|                         let (next_level, x, y) = self.prev_end(); | ||||
|                         player_level = next_level; | ||||
|                         self.get_mutable_player().get_position().set(next_level, x, y); | ||||
|                     } | ||||
|                     _ => {} | ||||
|                 } | ||||
|                 StructureElement::StairUp => { | ||||
|                     (dx, dy) = (0, 0); | ||||
|                     let (next_level, x, y) = self.prev_end(); | ||||
|                     player_level = next_level; | ||||
|                     self.messages | ||||
|                         .insert(0, format!("you climb up to level {}.", next_level + 1)); | ||||
|                     self.get_mutable_player() | ||||
|                         .get_position() | ||||
|                         .set(next_level, x, y); | ||||
|                 } | ||||
|                 _ => {} | ||||
|             } | ||||
|         }; | ||||
|             _ => {} | ||||
|         } | ||||
| 
 | ||||
|         // update position and discover area on new position
 | ||||
|         let (new_x, new_y) = self.get_mutable_player().get_position().change(dx, dy); | ||||
|         self.get_mutable_level(player_level) | ||||
|             .discover(&Position::new(player_level, new_x, new_y)); | ||||
|         self.get_mutable_level(player_level).discover(&Position::new(player_level, new_x, new_y)); | ||||
|         (-dx, -dy) | ||||
|     } | ||||
| 
 | ||||
|     pub fn player_fights_monster(&mut self) -> bool { | ||||
|         let player_pos = &self.player.get_immutable_position(); | ||||
|         let player_level = player_pos.get_level(); | ||||
| @ -164,42 +165,12 @@ impl Game { | ||||
|         match m { | ||||
|             None => {} | ||||
|             Some(m) => { | ||||
|                 let player_dmg = self.player.damage().saturating_sub(m.defense()); | ||||
|                 m.decrease_life(player_dmg); | ||||
|                 // TODO fight the monster
 | ||||
|                 self.player.change_life(-1); | ||||
|                 m.decrease_life(1); | ||||
|                 // monster died, player gains experience
 | ||||
|                 if m.is_dead() { | ||||
|                     let level_up: bool = self.player.gain_experience(m.get_experience_gain()); | ||||
|                     self.messages.insert( | ||||
|                         0, | ||||
|                         format!( | ||||
|                             "you hit {} for {} damage and kill it.", | ||||
|                             m.get_name(), | ||||
|                             player_dmg | ||||
|                         ) | ||||
|                         .to_string(), | ||||
|                     ); | ||||
|                     if level_up { | ||||
|                         self.messages.insert( | ||||
|                             0, | ||||
|                             format!( | ||||
|                                 "you reach a new experience level, it's now {}", | ||||
|                                 self.player.get_experience_level(), | ||||
|                             ) | ||||
|                             .to_string(), | ||||
|                         ); | ||||
|                     } | ||||
|                 } else { | ||||
|                     self.messages.insert( | ||||
|                         0, | ||||
|                         format!("you hit {} for {} damage.", m.get_name(), player_dmg).to_string(), | ||||
|                     ); | ||||
|                     let monster_dmg = m.damage().saturating_sub(self.player.defense()); | ||||
|                     self.player.change_life(-(monster_dmg as i16)); | ||||
| 
 | ||||
|                     self.messages.insert( | ||||
|                         0, | ||||
|                         format!("{} hits you for {} damage.", m.get_name(), monster_dmg) | ||||
|                             .to_string(), | ||||
|                     ); | ||||
|                     self.player.gain_experience(m.get_experience_gain()); | ||||
|                 } | ||||
|                 return m.is_dead(); | ||||
|             } | ||||
| @ -214,16 +185,16 @@ impl Game { | ||||
|         match a { | ||||
|             None => {} | ||||
|             Some(a) => { | ||||
|                 a.collect(&mut self.player, &mut self.messages); | ||||
|                 a.collect(&mut self.player); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     /// updates the player's current level. This will remove collected artifacts and dead monsters.
 | ||||
|     pub fn update_level(&mut self, ticks: u128) { | ||||
|     pub fn update_level(&mut self) { | ||||
|         let player_pos = &self.player.get_immutable_position(); | ||||
|         let player_level = player_pos.get_level(); | ||||
|         let level = &mut self.levels[player_level]; | ||||
|         level.update(ticks, &mut self.player, &mut self.messages); | ||||
|         level.update(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										277
									
								
								src/level.rs
									
									
									
									
									
								
							
							
						
						
									
										277
									
								
								src/level.rs
									
									
									
									
									
								
							| @ -1,20 +1,16 @@ | ||||
| use std::cmp::{max, min}; | ||||
| 
 | ||||
| use rand::rngs::ThreadRng; | ||||
| use rand::Rng; | ||||
| 
 | ||||
| use crate::artifacts::Artifact; | ||||
| #[cfg(test)] | ||||
| use crate::artifacts::{Chest, Potion}; | ||||
| use crate::constants::LEVEL_HEIGHT; | ||||
| use crate::constants::LEVEL_WIDTH; | ||||
| use crate::monsters::Monster; | ||||
| use crate::artifacts::Artifact; | ||||
| use crate::monster::Monster; | ||||
| #[cfg(test)] | ||||
| use crate::monsters::{Orc, Rat}; | ||||
| use crate::player::Player; | ||||
| use crate::position::HasPosition; | ||||
| use crate::monster::Rat; | ||||
| use crate::position::Position; | ||||
| 
 | ||||
| pub const LEVEL_WIDTH: usize = 50; | ||||
| pub const LEVEL_HEIGHT: usize = 25; | ||||
| 
 | ||||
| #[derive(Copy, Clone, Debug, PartialEq)] | ||||
| pub enum StructureElement { | ||||
|     Start, | ||||
| @ -26,9 +22,6 @@ pub enum StructureElement { | ||||
|     Unknown, | ||||
| } | ||||
| 
 | ||||
| type PossibleArtifact<'a> = Option<&'a mut Box<(dyn Artifact + 'static)>>; | ||||
| type PossibleMonster<'a> = Option<&'a mut Box<(dyn Monster + 'static)>>; | ||||
| 
 | ||||
| pub struct Level { | ||||
|     pub(crate) level: usize, | ||||
|     pub(crate) structure: [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH], | ||||
| @ -39,15 +32,28 @@ pub struct Level { | ||||
|     pub(crate) start: (usize, usize), | ||||
|     /// the position of the end in the level (either stair down or end point)
 | ||||
|     pub(crate) end: (usize, usize), | ||||
|     pub(crate) rng: ThreadRng, | ||||
| } | ||||
| 
 | ||||
| impl Level { | ||||
|     pub fn get_element( | ||||
|         &mut self, | ||||
|         x: i16, | ||||
|         y: i16, | ||||
|     ) -> (Option<StructureElement>, PossibleMonster, PossibleArtifact) { | ||||
|     #[cfg(test)] | ||||
|     pub fn new(level: usize) -> Level { | ||||
|         let mut s = [[StructureElement::Wall; LEVEL_HEIGHT]; LEVEL_WIDTH]; | ||||
|         for x in 2..LEVEL_WIDTH - 2 { | ||||
|             for y in 2..LEVEL_HEIGHT - 2 { | ||||
|                 s[x][y] = StructureElement::Floor; | ||||
|             } | ||||
|         } | ||||
|         Level { | ||||
|             level, | ||||
|             structure: s, | ||||
|             discovered: [[false; LEVEL_HEIGHT]; LEVEL_WIDTH], | ||||
|             monsters: Vec::with_capacity(10), | ||||
|             artifacts: Vec::with_capacity(10), | ||||
|             start: (0, 0), | ||||
|             end: (0, 0), | ||||
|         } | ||||
|     } | ||||
|     pub fn get_element(&mut self, x: i16, y: i16) -> (Option<StructureElement>, Option<&mut Box<(dyn Monster + 'static)>>, Option<&mut Box<(dyn Artifact + 'static)>>) { | ||||
|         if x < 0 || y < 0 { | ||||
|             return (None, None, None); | ||||
|         } | ||||
| @ -57,7 +63,6 @@ impl Level { | ||||
|             return (None, None, None); | ||||
|         } | ||||
|         if !self.discovered[x][y] { | ||||
|             // #[cfg(test)]
 | ||||
|             return (Some(StructureElement::Unknown), None, None); | ||||
|         } | ||||
|         let search_pos = &Position::new(self.level, x, y); | ||||
| @ -75,6 +80,33 @@ impl Level { | ||||
|         } | ||||
|         (Some(self.structure[x][y]), res_m, res_a) | ||||
|     } | ||||
|     #[cfg(test)] | ||||
|     pub fn add_monster(&mut self, mut monster: impl Monster + 'static) -> Result<(), String> { | ||||
|         if self.level != monster.get_position().get_level() { | ||||
|             return Err("Wrong Level".to_string()); | ||||
|         } | ||||
|         for m in &mut self.monsters { | ||||
|             if m.get_position() == monster.get_position() { | ||||
|                 return Err("Position already used".to_string()); | ||||
|             } | ||||
|         } | ||||
|         self.monsters.push(Box::new(monster)); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     #[cfg(test)] | ||||
|     pub fn add_artifact(&mut self, artifact: impl Artifact + 'static) -> Result<(), String> { | ||||
|         if self.level != artifact.get_immutable_position().get_level() { | ||||
|             return Err("Wrong Level".to_string()); | ||||
|         } | ||||
|         for a in &mut self.artifacts { | ||||
|             if a.get_immutable_position() == artifact.get_immutable_position() { | ||||
|                 return Err("Position already used".to_string()); | ||||
|             } | ||||
|         } | ||||
|         self.artifacts.push(Box::new(artifact)); | ||||
|         Ok(()) | ||||
|     } | ||||
|     /// discover the area with in the level around the given position
 | ||||
|     pub fn discover(&mut self, pos: &Position) { | ||||
|         let x = pos.get_x(); | ||||
| @ -119,104 +151,14 @@ impl Level { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     pub fn update(&mut self, ticks: u128, player: &mut Player, messages: &mut Vec<String>) { | ||||
| 
 | ||||
|     pub fn update(&mut self) { | ||||
|         for (index, a) in &mut self.artifacts.iter().enumerate() { | ||||
|             if a.was_collected() { | ||||
|                 self.artifacts.remove(index); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         for index in 0..self.monsters.len() { | ||||
|             if self.monsters[index].is_dead() { | ||||
|                 continue; | ||||
|             } | ||||
|             if ticks % self.monsters[index].get_ticks_between_steps() != 0 { | ||||
|                 continue; | ||||
|             } | ||||
|             loop { | ||||
|                 // calculate the direction the monster will try to walk
 | ||||
|                 let (dx, dy) = match self.rng.gen_range(0..5) { | ||||
|                     1 => (1, 0), | ||||
|                     2 => (-1, 0), | ||||
|                     3 => (0, 1), | ||||
|                     4 => (0, -1), | ||||
|                     _ => (0, 0), | ||||
|                 }; | ||||
|                 if self.can_monster_move(self.monsters[index].as_ref(), dx, dy) { | ||||
|                     let (new_x, new_y) = self.monsters[index].get_position().change(dx, dy); | ||||
|                     if player.get_immutable_position().get_x() == new_x | ||||
|                         && player.get_immutable_position().get_y() == new_y | ||||
|                     { | ||||
|                         let monster_dmg = self.monsters[index] | ||||
|                             .damage() | ||||
|                             .saturating_sub(player.defense()); | ||||
|                         player.change_life(-(monster_dmg as i16)); | ||||
|                         if player.is_dead() { | ||||
|                             messages.insert( | ||||
|                                 0, | ||||
|                                 format!( | ||||
|                                     "{} hits you for {} damage and kills you.", | ||||
|                                     self.monsters[index].get_name(), | ||||
|                                     monster_dmg | ||||
|                                 ) | ||||
|                                 .to_string(), | ||||
|                             ); | ||||
|                         } else { | ||||
|                             messages.insert( | ||||
|                                 0, | ||||
|                                 format!( | ||||
|                                     "{} hits you for {} damage.", | ||||
|                                     self.monsters[index].get_name(), | ||||
|                                     monster_dmg | ||||
|                                 ) | ||||
|                                 .to_string(), | ||||
|                             ); | ||||
|                             // player not dead => monster can occupy new position
 | ||||
|                             self.monsters[index].get_position().change(-dx, -dy); | ||||
|                             // player fights back
 | ||||
|                             let player_dmg = player | ||||
|                                 .damage() | ||||
|                                 .saturating_sub(self.monsters[index].defense()); | ||||
|                             self.monsters[index].decrease_life(player_dmg); | ||||
|                             if self.monsters[index].is_dead() { | ||||
|                                 let level_up: bool = player | ||||
|                                     .gain_experience(self.monsters[index].get_experience_gain()); | ||||
|                                 messages.insert( | ||||
|                                     0, | ||||
|                                     format!( | ||||
|                                         "you hit {} for {} damage and kill it.", | ||||
|                                         self.monsters[index].get_name(), | ||||
|                                         player_dmg | ||||
|                                     ) | ||||
|                                     .to_string(), | ||||
|                                 ); | ||||
|                                 if level_up { | ||||
|                                     messages.insert( | ||||
|                                         0, | ||||
|                                         format!( | ||||
|                                             "you reach a new experience level, it's now {}", | ||||
|                                             player.get_experience_level(), | ||||
|                                         ) | ||||
|                                         .to_string(), | ||||
|                                     ); | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 messages.insert( | ||||
|                                     0, | ||||
|                                     format!( | ||||
|                                         "you hit {} for {} damage.", | ||||
|                                         self.monsters[index].get_name(), | ||||
|                                         player_dmg | ||||
|                                     ) | ||||
|                                     .to_string(), | ||||
|                                 ); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         for (index, m) in &mut self.monsters.iter().enumerate() { | ||||
|             if m.is_dead() { | ||||
|                 self.monsters.remove(index); | ||||
| @ -224,82 +166,6 @@ impl Level { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn can_monster_move(&self, agent: &dyn Monster, dx: i16, dy: i16) -> bool { | ||||
|         let agent_pos = agent.get_immutable_position(); | ||||
|         let new_x: usize = (agent_pos.get_x() as i16 + dx) as usize; | ||||
|         let new_y: usize = (agent_pos.get_y() as i16 + dy) as usize; | ||||
| 
 | ||||
|         // verify we cannot leave the playing field
 | ||||
|         if new_x >= LEVEL_WIDTH || new_y >= LEVEL_HEIGHT { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         for index in 0..self.monsters.len() { | ||||
|             let pos = self.monsters[index].get_immutable_position(); | ||||
|             if pos.get_x() == new_x && pos.get_y() == new_y { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         self.structure[new_x][new_y] != StructureElement::Wall | ||||
|     } | ||||
|     pub fn can_player_move(&self, agent: &Player, dx: i16, dy: i16) -> bool { | ||||
|         let agent_pos = agent.get_immutable_position(); | ||||
|         let new_x: usize = (agent_pos.get_x() as i16 + dx) as usize; | ||||
|         let new_y: usize = (agent_pos.get_y() as i16 + dy) as usize; | ||||
| 
 | ||||
|         // verify we cannot leave the playing field
 | ||||
|         if new_x >= LEVEL_WIDTH || new_y >= LEVEL_HEIGHT { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         self.structure[new_x][new_y] != StructureElement::Wall | ||||
|     } | ||||
|     #[cfg(test)] | ||||
|     pub fn new(level: usize) -> Level { | ||||
|         let mut s = [[StructureElement::Wall; LEVEL_HEIGHT]; LEVEL_WIDTH]; | ||||
|         for x in 2..LEVEL_WIDTH - 2 { | ||||
|             for y in 2..LEVEL_HEIGHT - 2 { | ||||
|                 s[x][y] = StructureElement::Floor; | ||||
|             } | ||||
|         } | ||||
|         Level { | ||||
|             level, | ||||
|             structure: s, | ||||
|             discovered: [[false; LEVEL_HEIGHT]; LEVEL_WIDTH], | ||||
|             monsters: Vec::with_capacity(10), | ||||
|             artifacts: Vec::with_capacity(10), | ||||
|             start: (0, 0), | ||||
|             end: (0, 0), | ||||
|             rng: rand::thread_rng(), | ||||
|         } | ||||
|     } | ||||
|     #[cfg(test)] | ||||
|     pub fn add_monster(&mut self, mut monster: impl Monster + 'static) -> Result<(), String> { | ||||
|         if self.level != monster.get_position().get_level() { | ||||
|             return Err("Wrong Level".to_string()); | ||||
|         } | ||||
|         for m in &mut self.monsters { | ||||
|             if m.get_position() == monster.get_position() { | ||||
|                 return Err("Position already used".to_string()); | ||||
|             } | ||||
|         } | ||||
|         self.monsters.push(Box::new(monster)); | ||||
|         Ok(()) | ||||
|     } | ||||
|     #[cfg(test)] | ||||
|     pub fn add_artifact(&mut self, artifact: impl Artifact + 'static) -> Result<(), String> { | ||||
|         if self.level != artifact.get_immutable_position().get_level() { | ||||
|             return Err("Wrong Level".to_string()); | ||||
|         } | ||||
|         for a in &mut self.artifacts { | ||||
|             if a.get_immutable_position() == artifact.get_immutable_position() { | ||||
|                 return Err("Position already used".to_string()); | ||||
|             } | ||||
|         } | ||||
|         self.artifacts.push(Box::new(artifact)); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| @ -307,16 +173,8 @@ fn test_get_element() { | ||||
|     let mut l = Level::new(0); | ||||
|     assert_eq!(l.get_element(-1, -1).0, None); | ||||
|     assert_eq!(l.get_element(0, 0).0.unwrap(), StructureElement::Unknown); | ||||
|     assert_eq!( | ||||
|         l.get_element(LEVEL_WIDTH as i16 - 1, LEVEL_HEIGHT as i16 - 1) | ||||
|             .0 | ||||
|             .unwrap(), | ||||
|         StructureElement::Unknown | ||||
|     ); | ||||
|     assert_eq!( | ||||
|         l.get_element(LEVEL_WIDTH as i16, LEVEL_HEIGHT as i16).0, | ||||
|         None | ||||
|     ); | ||||
|     assert_eq!(l.get_element(LEVEL_WIDTH as i16 - 1, LEVEL_HEIGHT as i16 - 1).0.unwrap(), StructureElement::Unknown); | ||||
|     assert_eq!(l.get_element(LEVEL_WIDTH as i16, LEVEL_HEIGHT as i16).0, None); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| @ -335,17 +193,20 @@ fn test_discover_get_element() { | ||||
| #[test] | ||||
| fn test_discover_can_add_monster() { | ||||
|     let mut l = Level::new(0); | ||||
|     let m = Rat::new_with_position(Position::new(1, 2, 3)); | ||||
|     let mut m = Rat::new(2); | ||||
|     m.get_position().set(1, 2, 3); | ||||
|     assert_eq!(l.add_monster(m), Err("Wrong Level".to_string())); | ||||
| 
 | ||||
|     let mut m = Orc::new_with_position(Position::new(0, 2, 3)); | ||||
|     let mut m = Rat::new(2); | ||||
|     m.get_position().set(0, 2, 3); | ||||
|     assert_eq!(l.add_monster(m), Ok(())); | ||||
| 
 | ||||
|     let m = Rat::new_with_position(Position::new(0, 2, 3)); | ||||
|     let mut m = Rat::new(2); | ||||
|     m.get_position().set(0, 2, 3); | ||||
|     assert_eq!(l.add_monster(m), Err("Position already used".to_string())); | ||||
| 
 | ||||
|     let m = Rat::new_with_position(Position::new(0, 2, 4)); | ||||
|     let mut m = Rat::new(2); | ||||
|     m.get_position().set(0, 2, 4); | ||||
|     assert_eq!(l.add_monster(m), Ok(())); | ||||
| } | ||||
| 
 | ||||
| @ -373,17 +234,18 @@ fn test_discover_get_monster() { | ||||
|     assert_eq!(l.get_element(10, 10).0.unwrap(), StructureElement::Floor); | ||||
|     assert!(l.get_element(10, 10).1.is_none()); | ||||
| 
 | ||||
|     let m = Rat::new_with_position(Position::new(0, 10, 10)); | ||||
|     let mut m = Rat::new(23); | ||||
|     m.get_position().set(0, 10, 10); | ||||
|     assert_eq!(l.add_monster(m), Ok(())); | ||||
| 
 | ||||
|     let elem = l.get_element(10, 10); | ||||
|     assert_eq!(elem.0.unwrap(), StructureElement::Floor); | ||||
|     assert!(elem.1.is_some()); | ||||
|     let m = elem.1.unwrap(); | ||||
|     assert_eq!(m.get_life(), 2); | ||||
|     assert_eq!(m.get_life(), 23); | ||||
| 
 | ||||
|     m.decrease_life(2); | ||||
|     assert_eq!(l.get_element(10, 10).1.unwrap().get_life(), 0); | ||||
|     assert_eq!(l.get_element(10, 10).1.unwrap().get_life(), 21); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| @ -408,7 +270,8 @@ fn test_discover_get_monster_can_move() { | ||||
|     let p = Position::new(0, 10, 10); | ||||
|     l.discover(&p); | ||||
| 
 | ||||
|     let m = Rat::new_with_position(Position::new(0, 10, 10)); | ||||
|     let mut m = Rat::new(23); | ||||
|     m.get_position().set(0, 10, 10); | ||||
|     l.add_monster(m).expect("Panic because of"); | ||||
| 
 | ||||
|     let m = l.get_element(10, 10).1.unwrap(); | ||||
| @ -418,5 +281,5 @@ fn test_discover_get_monster_can_move() { | ||||
|     assert!(m.is_none()); | ||||
|     let m = l.get_element(11, 11).1; | ||||
|     assert!(m.is_some()); | ||||
|     assert_eq!(m.unwrap().get_life(), 2); | ||||
|     assert_eq!(m.unwrap().get_life(), 23); | ||||
| } | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,30 +0,0 @@ | ||||
| pub struct LevelLadder { | ||||
|     curr: usize, | ||||
|     next: usize, | ||||
| } | ||||
| 
 | ||||
| impl Iterator for LevelLadder { | ||||
|     type Item = usize; | ||||
| 
 | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
|         let current = self.curr; | ||||
| 
 | ||||
|         self.curr = self.next; | ||||
|         self.next += current; | ||||
| 
 | ||||
|         // Since there's no endpoint to a Fibonacci sequence, the `Iterator`
 | ||||
|         // will never return `None`, and `Some` is always returned.
 | ||||
|         Some(current * 30) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Returns a Fibonacci sequence generator
 | ||||
| pub fn get_level_ladder() -> LevelLadder { | ||||
|     LevelLadder { curr: 1, next: 2 } | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_level_ladder() { | ||||
|     let mut iter = get_level_ladder(); | ||||
|     assert_eq!(iter.next(), Some(30)); | ||||
| } | ||||
| @ -1,27 +1,22 @@ | ||||
| use ratatui::buffer::Buffer; | ||||
| use ratatui::layout::Rect; | ||||
| use ratatui::style::{Color, Modifier, Style}; | ||||
| use ratatui::style::Color; | ||||
| use ratatui::widgets::{StatefulWidget, Widget}; | ||||
| 
 | ||||
| use crate::game::Game; | ||||
| use crate::level::StructureElement; | ||||
| use crate::position::HasPosition; | ||||
| 
 | ||||
| const FG_BROWN: Color = Color::Rgb(140, 34, 0); | ||||
| const BACKGROUND: Color = Color::Black; | ||||
| const FG_BROWN: Color = Color::Rgb(186, 74, 0); | ||||
| 
 | ||||
| pub struct LevelWidget {} | ||||
| 
 | ||||
| impl LevelWidget { | ||||
|     fn set_cell(&self, buf: &mut Buffer, x: u16, y: u16, symbol: &str, fg: Color, bg: Color) { | ||||
|         buf[(x, y)].set_symbol(symbol).set_bg(bg).set_fg(fg); | ||||
|     } | ||||
|     fn set_bold_cell(&self, buf: &mut Buffer, x: u16, y: u16, symbol: &str, fg: Color, bg: Color) { | ||||
|         buf[(x, y)] | ||||
|             .set_symbol(symbol) | ||||
|             .set_bg(bg) | ||||
|             .set_fg(fg) | ||||
|             .set_style(Style::new().add_modifier(Modifier::BOLD)); | ||||
|         buf. | ||||
|             get_mut(x, y). | ||||
|             set_symbol(symbol). | ||||
|             set_bg(bg). | ||||
|             set_fg(fg); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -47,36 +42,36 @@ impl StatefulWidget for LevelWidget { | ||||
|                         (Some(structure_element), None, None) => { | ||||
|                             match structure_element { | ||||
|                                 StructureElement::Start => { | ||||
|                                     self.set_cell(buf, x, y, "α", Color::White, BACKGROUND); | ||||
|                                     self.set_cell(buf, x, y, "α", Color::Black, Color::Gray); | ||||
|                                 } | ||||
|                                 StructureElement::End => { | ||||
|                                     self.set_cell(buf, x, y, "Ω", Color::White, BACKGROUND); | ||||
|                                     self.set_cell(buf, x, y, "Ω", Color::Black, Color::Gray); | ||||
|                                 } | ||||
|                                 StructureElement::Wall => { | ||||
|                                     // TODO add fancy walls with https://en.wikipedia.org/wiki/Box-drawing_characters
 | ||||
|                                     self.set_cell(buf, x, y, "#", FG_BROWN, FG_BROWN); | ||||
|                                     // TODO add fancy walls
 | ||||
|                                     self.set_cell(buf, x, y, "#", FG_BROWN, Color::Gray); | ||||
|                                 } | ||||
|                                 StructureElement::Floor => { | ||||
|                                     self.set_cell(buf, x, y, " ", FG_BROWN, BACKGROUND); | ||||
|                                     self.set_cell(buf, x, y, " ", FG_BROWN, Color::Gray); | ||||
|                                 } | ||||
|                                 StructureElement::StairDown => { | ||||
|                                     self.set_cell(buf, x, y, ">", Color::White, BACKGROUND); | ||||
|                                     self.set_cell(buf, x, y, ">", Color::Black, Color::Gray); | ||||
|                                 } | ||||
|                                 StructureElement::StairUp => { | ||||
|                                     self.set_cell(buf, x, y, "<", Color::White, BACKGROUND); | ||||
|                                     self.set_cell(buf, x, y, "<", Color::Black, Color::Gray); | ||||
|                                 } | ||||
|                                 StructureElement::Unknown => { | ||||
|                                     self.set_cell(buf, x, y, "▒", Color::Gray, BACKGROUND); | ||||
|                                     self.set_cell(buf, x, y, "▒", Color::DarkGray, Color::Gray); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         (_, Some(m), _) => { | ||||
|                             let (s, c) = m.get_representation(); | ||||
|                             self.set_cell(buf, x, y, s, c, BACKGROUND); | ||||
|                             self.set_cell(buf, x, y, s, c, Color::Gray); | ||||
|                         } | ||||
|                         (_, _, Some(t)) => { | ||||
|                             let (s, c) = t.get_representation(); | ||||
|                             self.set_bold_cell(buf, x, y, s, c, BACKGROUND); | ||||
|                             self.set_cell(buf, x, y, s, c, Color::Gray); | ||||
|                         } | ||||
|                         (None, None, None) => {} | ||||
|                     }; | ||||
| @ -88,7 +83,7 @@ impl StatefulWidget for LevelWidget { | ||||
|             let player_pos = player.get_immutable_position(); | ||||
|             let player_x = al + player_pos.get_x() as u16; | ||||
|             let player_y = at + player_pos.get_y() as u16; | ||||
|             self.set_cell(buf, player_x, player_y, "8", Color::LightRed, BACKGROUND); | ||||
|             self.set_cell(buf, player_x, player_y, "8", Color::Red, Color::Gray); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										234
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										234
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -1,216 +1,113 @@ | ||||
| use std::io::stdout; | ||||
| use std::io::Result; | ||||
| use std::time::Instant; | ||||
| use std::io::stdout; | ||||
| 
 | ||||
| use constants::FRAME_LENGTH; | ||||
| use constants::LEVELS; | ||||
| use constants::LEVEL_HEIGHT; | ||||
| use constants::LEVEL_WIDTH; | ||||
| use constants::MIN_HEIGHT; | ||||
| use constants::MIN_WIDTH; | ||||
| use crossterm::{ | ||||
|     event::{self, KeyCode, KeyEventKind}, | ||||
|     terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, | ||||
|     ExecutableCommand, | ||||
|     terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, | ||||
| }; | ||||
| use ratatui::prelude::*; | ||||
| use ratatui::widgets::{Block, BorderType, Borders, Wrap}; | ||||
| use ratatui::{ | ||||
|     prelude::{CrosstermBackend, Terminal}, | ||||
|     prelude::{CrosstermBackend, Stylize, Terminal}, | ||||
|     widgets::Paragraph, | ||||
| }; | ||||
| use ratatui::prelude::*; | ||||
| use ratatui::widgets::{Block, Borders, BorderType, Wrap}; | ||||
| use ratatui::widgets::block::{Position, Title}; | ||||
| use whoami::realname; | ||||
| 
 | ||||
| use crate::game::{Game, GameState}; | ||||
| use crate::level_widget::LevelWidget; | ||||
| // use crate::level_widget::LevelWidget;
 | ||||
| use crate::player::Player; | ||||
| use crate::position::HasPosition; | ||||
| 
 | ||||
| mod artifacts; | ||||
| mod constants; | ||||
| mod game; | ||||
| mod level; | ||||
| mod level_generator; | ||||
| mod level_ladder; | ||||
| mod level_widget; | ||||
| mod monsters; | ||||
| mod player; | ||||
| mod level; | ||||
| mod position; | ||||
| mod room; | ||||
| 
 | ||||
| const BACKGROUND_COLOR: Color = Color::DarkGray; | ||||
| const PANEL_BACKGROUND_COLOR: Color = Color::Black; | ||||
| mod level_widget; | ||||
| mod level_generator; | ||||
| mod artifacts; | ||||
| mod monster; | ||||
| 
 | ||||
| //
 | ||||
| fn main() -> Result<()> { | ||||
|     let mut game = Game::new(Player::new(realname().as_str(), 30)); | ||||
|     let mut game = Game::new(Player::new(realname().as_str(), 10)); | ||||
| 
 | ||||
|     stdout().execute(EnterAlternateScreen)?; | ||||
|     enable_raw_mode()?; | ||||
|     let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; | ||||
|     terminal.clear()?; | ||||
| 
 | ||||
|     let start_time = Instant::now(); | ||||
|     let mut ticks = 0; | ||||
|     loop { | ||||
|         terminal.draw(|frame| { | ||||
|             let mut area = frame.area(); | ||||
|             frame.render_widget( | ||||
|                 Block::default().style(Style::default().bg(BACKGROUND_COLOR)), | ||||
|                 area, | ||||
|             ); | ||||
|             let mut area = frame.size(); | ||||
|             frame.render_widget(Block::default().style(Style::default().bg(Color::Green)), area); | ||||
| 
 | ||||
|             // don't draw stuff except an info box if the terminal is too small (less than 80x25)
 | ||||
|             // to prevent the read drawing code from crashing the game.
 | ||||
|             if area.width < MIN_WIDTH || area.height < MIN_HEIGHT { | ||||
|                 let block = Block::default() | ||||
|                     .title("Info") | ||||
|                     .borders(Borders::ALL) | ||||
|                     .border_style(Style::default().fg(Color::White)) | ||||
|                     .border_type(BorderType::Rounded) | ||||
|                     .style(Style::default().bg(Color::Black)); | ||||
|                 let paragraph = Paragraph::new(format!( | ||||
|                     "Terminal needs to be at leas {}x{}!", | ||||
|                     MIN_WIDTH, MIN_HEIGHT | ||||
|                 )) | ||||
|                 .block(block) | ||||
|                 .wrap(Wrap { trim: true }); | ||||
|                 frame.render_widget(paragraph, area); | ||||
|                 return; | ||||
|             if area.width > 80 { | ||||
|                 area.x = (area.width - 80) / 2; | ||||
|                 area.width = 80; | ||||
|             } | ||||
| 
 | ||||
|             if area.width > MIN_WIDTH { | ||||
|                 area.x = (area.width - MIN_WIDTH) / 2; | ||||
|                 area.width = MIN_WIDTH; | ||||
|             } | ||||
|             if area.height > MIN_HEIGHT { | ||||
|                 area.y = (area.height - LEVEL_HEIGHT as u16) / 2; | ||||
|                 area.height = LEVEL_HEIGHT as u16; | ||||
|             if area.height > 25 { | ||||
|                 area.y = (area.height - 25) / 2; | ||||
|                 area.height = 25; | ||||
|             } | ||||
| 
 | ||||
|             let map_area = Rect { | ||||
|                 x: area.x, | ||||
|                 y: area.y, | ||||
|                 width: LEVEL_WIDTH as u16, | ||||
|                 height: LEVEL_HEIGHT as u16, | ||||
|                 width: level::LEVEL_WIDTH as u16, | ||||
|                 height: level::LEVEL_HEIGHT as u16, | ||||
|             }; | ||||
|             frame.render_stateful_widget(LevelWidget {}, map_area, &mut game); | ||||
| 
 | ||||
|             let stats_area = Rect { | ||||
|                 x: area.x + map_area.width, | ||||
|                 x: area.x + 50, | ||||
|                 y: area.y, | ||||
|                 width: MIN_WIDTH - map_area.width, | ||||
|                 height: map_area.height / 2 + 1, | ||||
|                 width: 30, | ||||
|                 height: 25, | ||||
|             }; | ||||
|             let block = Block::default() | ||||
|                 .title_top( | ||||
|                     Line::from(format!(" {} ", game.get_player().get_name())).centered() | ||||
|                 ) | ||||
|                 .borders(Borders::TOP) | ||||
|                 .border_style(Style::default().fg(Color::White)) | ||||
|                 .border_type(BorderType::Rounded) | ||||
|                 .style(Style::default().bg(PANEL_BACKGROUND_COLOR)); | ||||
|             frame.render_widget( | ||||
|                 Paragraph::new(format!( | ||||
|                     "Health: {} of {}\nDefense: {}\nExp: {}\nGold: {}\nLevel: {} of {}\nInventory used: {} of {}", | ||||
|                     game.get_player().get_life(), | ||||
|                     game.get_player().get_max_life(), | ||||
|                     game.get_player().defense(), | ||||
|                     game.get_player().get_experience(), | ||||
|                     game.get_player().get_gold(), | ||||
|                     game.get_player().get_immutable_position().get_level() + 1, | ||||
|                     LEVELS, | ||||
|                     game.get_player().inventory_size().0, | ||||
|                     game.get_player().inventory_size().1, | ||||
|                 )) | ||||
|                 .block(block) | ||||
|                 .wrap(Wrap { trim: true }), | ||||
|                 Paragraph::new(format!("{}\nHealth: {}/{}\nExp: {}\nGold: {}\nLevel: {}", | ||||
|                                        game.get_player().get_name(), | ||||
|                                        game.get_player().get_life(), | ||||
|                                        game.get_player().get_max_life(), | ||||
|                                        game.get_player().get_experience(), | ||||
|                                        game.get_player().get_gold(), | ||||
|                                        game.get_player().get_immutable_position().get_level())) | ||||
|                     .white() | ||||
|                     .on_blue(), | ||||
|                 stats_area, | ||||
|             ); | ||||
|             let messages_area = Rect { | ||||
|                 x: area.x + map_area.width, | ||||
|                 y: area.y + map_area.height / 2 + 1, | ||||
|                 width: MIN_WIDTH - map_area.width, | ||||
|                 height: map_area.height / 2, | ||||
|             }; | ||||
|             // Display the latest messages from the game to the user
 | ||||
|             let block = Block::default() | ||||
|                 .title_top( | ||||
|                     Line::from(" messages ").centered() | ||||
|                 ) | ||||
|                 .borders(Borders::TOP) | ||||
|                 .border_style(Style::default().fg(Color::White)) | ||||
|                 .border_type(BorderType::Rounded) | ||||
|                 .style(Style::default().bg(PANEL_BACKGROUND_COLOR)); | ||||
| 
 | ||||
|             let paragraph1 = if game.messages.is_empty() { | ||||
|                 "".to_string() | ||||
|             } else { | ||||
|                 format!("> {}", game.messages.join("\n> ")) | ||||
|             }; | ||||
|             frame.render_widget( | ||||
|                 Paragraph::new(paragraph1) | ||||
|                     .block(block) | ||||
|                     .wrap(Wrap { trim: true }), | ||||
|                 messages_area, | ||||
|             ); | ||||
|         })?; | ||||
|         if event::poll(std::time::Duration::from_millis(FRAME_LENGTH))? { | ||||
|         if event::poll(std::time::Duration::from_millis(16))? { | ||||
|             if let event::Event::Key(key) = event::read()? { | ||||
|                 if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { | ||||
|                     break; | ||||
|                 } | ||||
|                 if key.kind == KeyEventKind::Press { | ||||
|                     match key.code { | ||||
|                         KeyCode::Char('v') => { | ||||
|                             game.messages.insert( | ||||
|                                 0, | ||||
|                                 format!("You are playing version '{}'.", env!("GIT_HASH")) | ||||
|                                     .to_string(), | ||||
|                             ); | ||||
|                         } | ||||
|                         KeyCode::Char('p') => { | ||||
|                             let gained_health = game.get_mutable_player().consume_inventory(); | ||||
|                             if gained_health > 0 { | ||||
|                                 game.messages.insert( | ||||
|                                     0, | ||||
|                                     format!( | ||||
|                                         "used a potion from inventory and gained {} health.", | ||||
|                                         gained_health | ||||
|                                     ) | ||||
|                                     .to_string(), | ||||
|                                 ); | ||||
|                             } | ||||
|                         } | ||||
|                         KeyCode::Char('q') => { | ||||
|                             break; | ||||
|                         } | ||||
|                         KeyCode::Left | KeyCode::Down | KeyCode::Right | KeyCode::Up => { | ||||
|                             let new_pos = match key.code { | ||||
|                                 KeyCode::Left => game.move_player(-1, 0), | ||||
|                                 KeyCode::Right => game.move_player(1, 0), | ||||
|                                 KeyCode::Up => game.move_player(0, -1), | ||||
|                                 KeyCode::Down => game.move_player(0, 1), | ||||
|                                 _ => (0, 0), | ||||
|                             }; | ||||
|                             if !game.player_fights_monster() { | ||||
|                                 // player attacked monster but did not kill it
 | ||||
|                                 game.move_player(new_pos.0, new_pos.1); | ||||
|                             } | ||||
|                             game.player_collects_artifact(); | ||||
|                         } | ||||
|                         _ => {} | ||||
|                     let new_pos = match key.code { | ||||
|                         KeyCode::Left => { game.move_player(-1, 0) } | ||||
|                         KeyCode::Right => { game.move_player(1, 0) } | ||||
|                         KeyCode::Up => { game.move_player(0, -1) } | ||||
|                         KeyCode::Down => { game.move_player(0, 1) } | ||||
|                         _ => { (0, 0) } | ||||
|                     }; | ||||
|                     if !game.player_fights_monster() { | ||||
|                         // player attacked monster but did not kill it
 | ||||
|                         game.move_player(new_pos.0, new_pos.1); | ||||
|                     } | ||||
|                     game.player_collects_artifact(); | ||||
|                     game.update_level(); | ||||
|                     if game.get_game_state() != GameState::RUNNING { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         game.update_level(ticks); | ||||
|         if game.get_game_state() != GameState::Running { | ||||
|             break; | ||||
|         } | ||||
|         ticks += 1; | ||||
|     } | ||||
|     let playtime = start_time.elapsed(); | ||||
|     loop { | ||||
|         let _ = terminal.draw(|frame| { | ||||
|             let mut area = frame.area(); | ||||
|             let mut area = frame.size(); | ||||
|             let w = area.width / 2; | ||||
|             let h = area.height / 2; | ||||
|             area.x += w - 20; | ||||
| @ -218,31 +115,25 @@ fn main() -> Result<()> { | ||||
|             area.width = 40; | ||||
|             area.height = 20; | ||||
|             let block = Block::default() | ||||
|                 .title_top(Line::from(" Game ended ").centered()) | ||||
|                 .title_bottom(Line::from("Press `q` to quit!")) | ||||
|                 .title("Game ended") | ||||
|                 .title(Title::from("Press `q` to quit!").position(Position::Bottom)) | ||||
|                 .borders(Borders::ALL) | ||||
|                 .border_style(Style::default().fg(Color::White)) | ||||
|                 .border_type(BorderType::Rounded) | ||||
|                 .style(Style::default().bg(Color::Black)); | ||||
|             let mut text = match game.get_game_state() { | ||||
|                 GameState::Running => { | ||||
|                 GameState::RUNNING => { | ||||
|                     "Quitting is for cowards! You'll better try again!".to_string() | ||||
|                 } | ||||
|                 GameState::Lost => { | ||||
|                 GameState::LOST => { | ||||
|                     "Sorry, you died in the dungeon. Better luck next time!".to_string() | ||||
|                 } | ||||
|                 GameState::Won => { | ||||
|                     "Congratulation! You mastered your way through the dungeon and won the game." | ||||
|                         .to_string() | ||||
|                 GameState::WON => { | ||||
|                     "Congratulation! You mastered your way through the dungeon and won the game.".to_string() | ||||
|                 } | ||||
|             }; | ||||
|             text += format!( | ||||
|                 "\nYou gained {} experience.", | ||||
|                 game.get_player().get_experience() | ||||
|             ) | ||||
|             .as_str(); | ||||
|             text += format!("\nYou gained {} experience.", game.get_player().get_experience()).as_str(); | ||||
|             text += format!("\nYou collected {} gold.", game.get_player().get_gold()).as_str(); | ||||
|             text += format!("\nYou played {} seconds.", playtime.as_secs()).as_str(); | ||||
|             let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); | ||||
|             frame.render_widget(paragraph, area); | ||||
|         }); | ||||
| @ -254,6 +145,7 @@ fn main() -> Result<()> { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     stdout().execute(LeaveAlternateScreen)?; | ||||
|     disable_raw_mode()?; | ||||
|     Ok(()) | ||||
|  | ||||
							
								
								
									
										130
									
								
								src/monster.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/monster.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | ||||
| use ratatui::prelude::Color; | ||||
| 
 | ||||
| use crate::position::Position; | ||||
| 
 | ||||
| pub trait Monster { | ||||
|     fn is_dead(&self) -> bool; | ||||
|     fn get_representation(&self) -> (&str, Color); | ||||
|     fn decrease_life(&mut self, by: usize); | ||||
|     fn get_immutable_position(&self) -> &Position; | ||||
|     fn get_experience_gain(&self) -> usize; | ||||
|     #[cfg(test)] | ||||
|     fn get_position(&mut self) -> &mut Position; | ||||
|     #[cfg(test)] | ||||
|     fn get_life(&self) -> usize; | ||||
| } | ||||
| 
 | ||||
| macro_rules! default_monster { | ||||
|     ($($t:ty),+ $(,)?) => ($( | ||||
|         impl Monster for $t { | ||||
|             fn is_dead(&self) -> bool { self.life <= 0 } | ||||
|             fn get_experience_gain(&self) -> usize { self.experience_gain } | ||||
|             fn get_representation(&self) -> (&str, Color) { (&self.symbol, self.color) } | ||||
|             fn decrease_life(&mut self, by: usize) { | ||||
|                 self.life = self.life.saturating_sub(by); | ||||
|             } | ||||
|             fn get_immutable_position(&self) -> &Position { | ||||
|                 &self.position | ||||
|             } | ||||
|             #[cfg(test)] | ||||
|             fn get_position(&mut self) -> &mut Position { | ||||
|                 &mut self.position | ||||
|             } | ||||
|             #[cfg(test)] | ||||
|             fn get_life(&self) -> usize { self.life } | ||||
|         } | ||||
|     )+) | ||||
| } | ||||
| 
 | ||||
| pub struct Rat { | ||||
|     life: usize, | ||||
|     position: Position, | ||||
|     symbol: String, | ||||
|     color: Color, | ||||
|     experience_gain: usize, | ||||
| } | ||||
| 
 | ||||
| impl Rat { | ||||
|     #[cfg(test)] | ||||
|     pub fn new(life: usize) -> Self { | ||||
|         Self { | ||||
|             life, | ||||
|             position: Position::new(0, 0, 0), | ||||
|             symbol: String::from("R"), | ||||
|             color: Color::Black, | ||||
|             experience_gain: 5, | ||||
|         } | ||||
|     } | ||||
|     pub fn new_with_position(position: Position) -> Self { | ||||
|         Self { | ||||
|             life: 2, | ||||
|             position, | ||||
|             symbol: String::from("R"), | ||||
|             color: Color::Black, | ||||
|             experience_gain: 5, | ||||
|         } | ||||
|     } | ||||
|     #[cfg(test)] | ||||
|     pub fn get_life(&self) -> usize { self.life } | ||||
| } | ||||
| default_monster!(Rat); | ||||
| 
 | ||||
| pub struct Orc { | ||||
|     life: usize, | ||||
|     position: Position, | ||||
|     symbol: String, | ||||
|     color: Color, | ||||
|     experience_gain: usize, | ||||
| } | ||||
| 
 | ||||
| impl Orc { | ||||
|     #[cfg(test)] | ||||
|     pub fn new(life: usize) -> Self { | ||||
|         Self { | ||||
|             life, | ||||
|             position: Position::new(0, 0, 0), | ||||
|             symbol: String::from("O"), | ||||
|             color: Color::DarkGray, | ||||
|             experience_gain: 10, | ||||
|         } | ||||
|     } | ||||
|     pub fn new_with_position(position: Position) -> Self { | ||||
|         Self { | ||||
|             life: 4, | ||||
|             position, | ||||
|             symbol: String::from("O"), | ||||
|             color: Color::DarkGray, | ||||
|             experience_gain: 10, | ||||
|         } | ||||
|     } | ||||
|     #[cfg(test)] | ||||
|     pub fn get_life(&self) -> usize { self.life } | ||||
| } | ||||
| 
 | ||||
| default_monster!(Orc); | ||||
| 
 | ||||
| 
 | ||||
| #[test] | ||||
| fn monsters_can_move() { | ||||
|     let mut m = Rat::new(2); | ||||
|     assert_eq!(m.get_position(), &Position::new(0, 0, 0)); | ||||
|     m.get_position().change(1, 2); | ||||
|     assert_eq!(m.get_position(), &Position::new(0, 1, 2)); | ||||
|     m.get_position().change(2, 1); | ||||
|     assert_eq!(m.get_position(), &Position::new(0, 3, 3)); | ||||
|     m.get_position().set(1, 2, 3); | ||||
|     m.get_position().change(2, 1); | ||||
|     assert_eq!(m.get_position(), &Position::new(1, 4, 4)); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn monsters_can_die() { | ||||
|     let mut m = Rat::new(2); | ||||
|     assert_eq!(m.get_life(), 2); | ||||
|     assert_eq!(m.is_dead(), false); | ||||
|     m.decrease_life(1); | ||||
|     assert_eq!(m.get_life(), 1); | ||||
|     m.decrease_life(2); | ||||
|     assert_eq!(m.get_life(), 0); | ||||
|     assert_eq!(m.is_dead(), true); | ||||
| } | ||||
							
								
								
									
										137
									
								
								src/monsters.rs
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								src/monsters.rs
									
									
									
									
									
								
							| @ -1,137 +0,0 @@ | ||||
| use std::ops::RangeInclusive; | ||||
| 
 | ||||
| use crate::position::{HasPosition, Position}; | ||||
| use macros::CreateMonsters; | ||||
| use rand::Rng; | ||||
| use ratatui::prelude::Color; | ||||
| 
 | ||||
| pub trait Monster: HasPosition { | ||||
|     fn get_name(&self) -> &str; | ||||
|     fn is_dead(&self) -> bool; | ||||
|     fn get_representation(&self) -> (&str, Color); | ||||
|     fn decrease_life(&mut self, by: usize); | ||||
|     // fn get_immutable_position(&self) -> &Position;
 | ||||
|     fn get_experience_gain(&self) -> usize; | ||||
|     fn get_ticks_between_steps(&self) -> u128; | ||||
|     #[cfg(test)] | ||||
|     fn get_life(&self) -> usize; | ||||
|     fn damage(&self) -> usize; | ||||
|     fn defense(&self) -> usize; | ||||
| } | ||||
| 
 | ||||
| #[derive(CreateMonsters, PartialEq, Eq, Hash, Clone, Copy)] | ||||
| pub enum MonsterTypes { | ||||
|     Rat, | ||||
|     Orc, | ||||
|     Snake, | ||||
|     Skeleton, | ||||
|     Spider, | ||||
| } | ||||
| 
 | ||||
| macro_rules! create_monster { | ||||
|     ($t:ident $(, $k:ident : $v:expr)*$(,)?) => ( | ||||
|         impl $t { | ||||
|             pub fn new_with_position(position: Position) -> Self { | ||||
|                 Self { | ||||
|                     position, | ||||
|                     $($k: $v,)* | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| create_monster!( | ||||
|     Rat, | ||||
|     name:"rat".to_string(), | ||||
|     life: 2, | ||||
|     symbol: String::from("r"), | ||||
|     color: Color::White, | ||||
|     experience_gain: 5, | ||||
|     ticks_between_steps: 5, | ||||
|     damage_range: 1..=2, | ||||
|     defense: 0, | ||||
| ); | ||||
| 
 | ||||
| create_monster!( | ||||
|     Spider, | ||||
|     name:"spider".to_string(), | ||||
|     life: 3, | ||||
|     symbol: String::from("s"), | ||||
|     color: Color::Yellow, | ||||
|     experience_gain: 7, | ||||
|     ticks_between_steps: 7, | ||||
|     damage_range: 2..=3, | ||||
|     defense: 0, | ||||
| ); | ||||
| 
 | ||||
| create_monster!( | ||||
|     Orc, | ||||
|     name: "orc".to_string(), | ||||
|     life: 4, | ||||
|     symbol: String::from("O"), | ||||
|     color: Color::Gray, | ||||
|     experience_gain: 10, | ||||
|     ticks_between_steps: 10, | ||||
|     damage_range: 2..=3, | ||||
|     defense: 0, | ||||
| ); | ||||
| 
 | ||||
| create_monster!( | ||||
|     Snake, | ||||
|     name: "snake".to_string(), | ||||
|     life: 3, | ||||
|     symbol: String::from("s"), | ||||
|     color: Color::White, | ||||
|     experience_gain: 10, | ||||
|     ticks_between_steps: 20, | ||||
|     damage_range: 1..=4, | ||||
|     defense: 0, | ||||
| ); | ||||
| 
 | ||||
| create_monster!( | ||||
|     Skeleton, | ||||
|     name: "skeleton".to_string(), | ||||
|     life: 3, | ||||
|     symbol: String::from("S"), | ||||
|     color: Color::Gray, | ||||
|     experience_gain: 20, | ||||
|     ticks_between_steps: 10, | ||||
|     damage_range: 1..=5, | ||||
|     defense: 0, | ||||
| ); | ||||
| 
 | ||||
| pub fn create_monster_by_type(monster_type: &MonsterTypes, position: Position) -> Box<dyn Monster> { | ||||
|     match monster_type { | ||||
|         MonsterTypes::Rat => Box::new(Rat::new_with_position(position)), | ||||
|         MonsterTypes::Orc => Box::new(Orc::new_with_position(position)), | ||||
|         MonsterTypes::Snake => Box::new(Snake::new_with_position(position)), | ||||
|         MonsterTypes::Skeleton => Box::new(Skeleton::new_with_position(position)), | ||||
|         MonsterTypes::Spider => Box::new(Spider::new_with_position(position)), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn monsters_can_move() { | ||||
|     let mut m = Rat::new_with_position(Position::new(0, 0, 0)); | ||||
|     assert_eq!(m.get_position(), &Position::new(0, 0, 0)); | ||||
|     m.get_position().change(1, 2); | ||||
|     assert_eq!(m.get_position(), &Position::new(0, 1, 2)); | ||||
|     m.get_position().change(2, 1); | ||||
|     assert_eq!(m.get_position(), &Position::new(0, 3, 3)); | ||||
|     m.get_position().set(1, 2, 3); | ||||
|     m.get_position().change(2, 1); | ||||
|     assert_eq!(m.get_position(), &Position::new(1, 4, 4)); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn monsters_can_die() { | ||||
|     let mut m = Rat::new_with_position(Position::new(0, 0, 0)); | ||||
|     assert_eq!(m.get_life(), 2); | ||||
|     assert_eq!(m.is_dead(), false); | ||||
|     m.decrease_life(1); | ||||
|     assert_eq!(m.get_life(), 1); | ||||
|     m.decrease_life(2); | ||||
|     assert_eq!(m.get_life(), 0); | ||||
|     assert_eq!(m.is_dead(), true); | ||||
| } | ||||
							
								
								
									
										122
									
								
								src/player.rs
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								src/player.rs
									
									
									
									
									
								
							| @ -1,9 +1,6 @@ | ||||
| use rand::Rng; | ||||
| use std::cmp::{max, min}; | ||||
| 
 | ||||
| use crate::artifacts::Potion; | ||||
| use crate::level_ladder::get_level_ladder; | ||||
| use crate::position::{HasPosition, Position}; | ||||
| use crate::position::Position; | ||||
| 
 | ||||
| pub struct Player { | ||||
|     name: String, | ||||
| @ -12,10 +9,6 @@ pub struct Player { | ||||
|     max_life: i16, | ||||
|     gold: usize, | ||||
|     experience: usize, | ||||
|     inventory: Vec<Potion>, | ||||
|     inventory_slots: usize, | ||||
|     level: usize, | ||||
|     defense: usize, | ||||
| } | ||||
| 
 | ||||
| impl Player { | ||||
| @ -27,14 +20,10 @@ impl Player { | ||||
|             max_life, | ||||
|             gold: 0, | ||||
|             experience: 0, | ||||
|             inventory: vec![], | ||||
|             inventory_slots: 2, | ||||
|             level: 1, | ||||
|             defense: 0, | ||||
|         } | ||||
|     } | ||||
|     pub fn get_name(&self) -> String { | ||||
|         self.name.clone() | ||||
|         return self.name.clone(); | ||||
|     } | ||||
|     pub fn change_life(&mut self, by: i16) { | ||||
|         self.life = max(0, min(self.max_life, self.life + by)); | ||||
| @ -42,96 +31,25 @@ impl Player { | ||||
|     pub fn get_life(&self) -> i16 { | ||||
|         self.life | ||||
|     } | ||||
|     /// returns true if the player is dead (life <= 0)
 | ||||
|     pub fn is_dead(&self) -> bool { | ||||
|         self.life <= 0 | ||||
|     } | ||||
|     /// returns true if the player's life is at maximum
 | ||||
|     pub fn is_healthy(&self) -> bool { | ||||
|         self.life == self.max_life | ||||
|     } | ||||
|     pub fn get_max_life(&self) -> i16 { | ||||
|         self.max_life | ||||
|     } | ||||
| 
 | ||||
|     /// add the given amount to the players gold stash
 | ||||
|     pub fn retrieve_gold(&mut self, amount: usize) { | ||||
|         self.gold += amount | ||||
|     } | ||||
| 
 | ||||
|     /// return the size of the players gold stash
 | ||||
|     pub fn get_gold(&self) -> usize { | ||||
|         self.gold | ||||
|     } | ||||
| 
 | ||||
|     pub fn gain_experience(&mut self, amount: usize) -> bool { | ||||
|         let mut result = false; | ||||
|         for (i, level_step) in get_level_ladder().enumerate() { | ||||
|             if self.experience <= level_step && self.experience + amount > level_step { | ||||
|                 self.level += 1; | ||||
|                 // make life gain depend on level?
 | ||||
|                 self.max_life += 5; | ||||
|                 self.defense += 1; | ||||
|                 self.life = self.max_life; | ||||
|                 result = true; | ||||
|                 break; | ||||
|             } | ||||
|             if i > self.level { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         self.experience += amount; | ||||
|         result | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_experience_level(&self) -> usize { | ||||
|         self.level | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_experience(&self) -> usize { | ||||
|         self.experience | ||||
|     } | ||||
| 
 | ||||
|     pub fn damage(&self) -> usize { | ||||
|         rand::thread_rng().gen_range(1..4) | ||||
|     } | ||||
| 
 | ||||
|     pub fn defense(&self) -> usize { | ||||
|         self.defense | ||||
|     } | ||||
|     
 | ||||
|     pub fn add_to_inventory(&mut self, potion: &Potion) -> bool { | ||||
|         if self.inventory.len() < self.inventory_slots { | ||||
|             self.inventory.push(*potion); | ||||
|             true | ||||
|         } else { | ||||
|             false | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn inventory_size(&self) -> (usize, usize) { | ||||
|         (self.inventory.len(), self.inventory_slots) | ||||
|     } | ||||
| 
 | ||||
|     pub fn consume_inventory(&mut self) -> usize { | ||||
|         if self.is_healthy() { | ||||
|             return 0; | ||||
|         } | ||||
|         if let Some(potion) = self.inventory.pop() { | ||||
|             self.change_life(potion.get_health() as i16); | ||||
|             return potion.get_health(); | ||||
|         } | ||||
|         0 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl HasPosition for Player { | ||||
|     fn get_position(&mut self) -> &mut Position { | ||||
|     pub fn get_position(&mut self) -> &mut Position { | ||||
|         &mut self.position | ||||
|     } | ||||
|     fn get_immutable_position(&self) -> &Position { | ||||
|     pub fn get_immutable_position(&self) -> &Position { | ||||
|         &self.position | ||||
|     } | ||||
| 
 | ||||
|     /// add the given amount to the players gold stash
 | ||||
|     pub fn retrieve_gold(&mut self, amount: usize) { self.gold += amount } | ||||
| 
 | ||||
|     /// return the size of the players gold stash
 | ||||
|     pub fn get_gold(&self) -> usize { self.gold } | ||||
| 
 | ||||
|     pub fn gain_experience(&mut self, amount: usize) { self.experience += amount } | ||||
| 
 | ||||
|     pub fn get_experience(&self) -> usize { self.experience } | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| @ -143,10 +61,6 @@ fn test_get_name() { | ||||
|         max_life: 10, | ||||
|         gold: 0, | ||||
|         experience: 0, | ||||
|         inventory: vec![], | ||||
|         inventory_slots: 1, | ||||
|         level: 0, | ||||
|         defense: 0, | ||||
|     }; | ||||
|     assert_eq!(p.get_name(), "Teddy Tester"); | ||||
| } | ||||
| @ -170,10 +84,6 @@ fn test_change_life() { | ||||
|         max_life: 10, | ||||
|         gold: 0, | ||||
|         experience: 0, | ||||
|         inventory: vec![], | ||||
|         inventory_slots: 1, | ||||
|         level: 0, | ||||
|         defense: 0, | ||||
|     }; | ||||
|     assert_eq!(p.get_life(), 5); | ||||
|     p.change_life(-2); | ||||
| @ -207,10 +117,6 @@ fn test_max_life() { | ||||
|         max_life: 10, | ||||
|         gold: 0, | ||||
|         experience: 0, | ||||
|         inventory: vec![], | ||||
|         inventory_slots: 1, | ||||
|         level: 0, | ||||
|         defense: 0, | ||||
|     }; | ||||
|     assert_eq!(p.get_max_life(), 10); | ||||
| } | ||||
|  | ||||
| @ -1,14 +1,6 @@ | ||||
| use std::cmp::max; | ||||
| 
 | ||||
| /// describes an character (PC or NPC) in the dungeon that has a position.
 | ||||
| pub trait HasPosition { | ||||
|     /// returns a mutable position
 | ||||
|     fn get_position(&mut self) -> &mut Position; | ||||
|     /// returns an immutable position
 | ||||
|     fn get_immutable_position(&self) -> &Position; | ||||
| } | ||||
| 
 | ||||
| #[derive(PartialEq, Debug, Clone, Copy)] | ||||
| #[derive(PartialEq, Debug)] | ||||
| pub struct Position { | ||||
|     level: usize, | ||||
|     x: usize, | ||||
| @ -17,7 +9,11 @@ pub struct Position { | ||||
| 
 | ||||
| impl Position { | ||||
|     pub fn new(level: usize, x: usize, y: usize) -> Self { | ||||
|         Self { level, x, y } | ||||
|         Self { | ||||
|             level, | ||||
|             x, | ||||
|             y, | ||||
|         } | ||||
|     } | ||||
|     pub fn change(&mut self, dx: i16, dy: i16) -> (usize, usize) { | ||||
|         self.x = max((self.x as i16) + dx, 0) as usize; | ||||
| @ -38,9 +34,7 @@ impl Position { | ||||
|         self.x | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_y(&self) -> usize { | ||||
|         self.y | ||||
|     } | ||||
|     pub fn get_y(&self) -> usize { self.y } | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
|  | ||||
							
								
								
									
										280
									
								
								src/room.rs
									
									
									
									
									
								
							
							
						
						
									
										280
									
								
								src/room.rs
									
									
									
									
									
								
							| @ -1,280 +0,0 @@ | ||||
| use std::{ | ||||
|     cmp::{max, min}, | ||||
|     ops::RangeInclusive, | ||||
| }; | ||||
| 
 | ||||
| use crate::{ | ||||
|     constants::{ | ||||
|         LEVEL_HEIGHT, LEVEL_WIDTH, ROOM_HEIGHT, ROOM_MIN_HEIGHT, ROOM_MIN_WIDTH, ROOM_WIDTH, | ||||
|     }, | ||||
|     level::StructureElement, | ||||
| }; | ||||
| use rand::rngs::ThreadRng; | ||||
| use rand::Rng; | ||||
| 
 | ||||
| #[derive(PartialEq, Copy, Clone, Eq, Hash, Debug)] | ||||
| pub enum RoomType { | ||||
|     Start, | ||||
|     End, | ||||
|     StairUp, | ||||
|     StairDown, | ||||
|     BasicRoom, | ||||
|     ArtifactRoom, | ||||
|     MonsterRoom, | ||||
|     EmptyRoom, | ||||
| } | ||||
| 
 | ||||
| #[derive(Copy, Clone, Debug)] | ||||
| pub struct Connection { | ||||
|     pub start_pos: (usize, usize), | ||||
|     pub end_pos: (usize, usize), | ||||
| } | ||||
| 
 | ||||
| impl Connection { | ||||
|     pub fn render( | ||||
|         &self, | ||||
|         rng: &mut ThreadRng, | ||||
|         tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH], | ||||
|     ) { | ||||
|         fn calc_range(a: usize, b: usize) -> RangeInclusive<usize> { | ||||
|             if a > b { | ||||
|                 b..=a | ||||
|             } else { | ||||
|                 a..=b | ||||
|             } | ||||
|         } | ||||
|         // the tuples are (col, row)
 | ||||
|         let abs_d_col = | ||||
|             max(self.start_pos.0, self.end_pos.0) - min(self.start_pos.0, self.end_pos.0); | ||||
| 
 | ||||
|         let abs_d_row = | ||||
|             max(self.start_pos.1, self.end_pos.1) - min(self.start_pos.1, self.end_pos.1); | ||||
| 
 | ||||
|         if abs_d_col == 0 { | ||||
|             for row in calc_range(self.end_pos.1, self.start_pos.1) { | ||||
|                 tgt[self.end_pos.0][row] = StructureElement::Floor; | ||||
|             } | ||||
|         } else if abs_d_row == 0 { | ||||
|             for col in calc_range(self.end_pos.0, self.start_pos.0) { | ||||
|                 tgt[col][self.end_pos.1] = StructureElement::Floor; | ||||
|             } | ||||
|         } else { | ||||
|             let d_row = self.end_pos.1 as i32 - self.start_pos.1 as i32; | ||||
|             if d_row > 0 { | ||||
|                 // more up/down
 | ||||
|                 let d_row = self.end_pos.1 - self.start_pos.1; | ||||
|                 let vert_offset = if d_row > 3 { | ||||
|                     rng.gen_range(1..=d_row - 1) | ||||
|                 } else { | ||||
|                     d_row / 2 | ||||
|                 }; | ||||
|                 for r in self.start_pos.1..=(self.start_pos.1 + vert_offset) { | ||||
|                     tgt[self.start_pos.0][r] = StructureElement::Floor; | ||||
|                 } | ||||
|                 for c in calc_range(self.end_pos.0, self.start_pos.0) { | ||||
|                     tgt[c][self.start_pos.1 + vert_offset] = StructureElement::Floor; | ||||
|                 } | ||||
|                 for r in (self.start_pos.1 + vert_offset)..=self.end_pos.1 { | ||||
|                     tgt[self.end_pos.0][r] = StructureElement::Floor; | ||||
|                 } | ||||
|             } else { | ||||
|                 // more left/right
 | ||||
|                 let d_col = self.end_pos.0 - self.start_pos.0; | ||||
|                 let horizont_offset = if d_col > 3 { | ||||
|                     rng.gen_range(1..=d_col - 1) | ||||
|                 } else { | ||||
|                     d_col / 2 | ||||
|                 }; | ||||
|                 for tgt_col in tgt | ||||
|                     .iter_mut() | ||||
|                     .skip(self.start_pos.0) | ||||
|                     .take(horizont_offset + 1) | ||||
|                 { | ||||
|                     tgt_col[self.start_pos.1] = StructureElement::Floor; | ||||
|                 } | ||||
|                 for r in calc_range(self.end_pos.1, self.start_pos.1) { | ||||
|                     tgt[self.start_pos.0 + horizont_offset][r] = StructureElement::Floor; | ||||
|                 } | ||||
|                 for tgt_col in tgt | ||||
|                     .iter_mut() | ||||
|                     .take(self.end_pos.0 + 1) | ||||
|                     .skip(self.start_pos.0 + horizont_offset) | ||||
|                 { | ||||
|                     tgt_col[self.end_pos.1] = StructureElement::Floor; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Copy, Clone, Debug)] | ||||
| pub struct Room { | ||||
|     pub kind: RoomType, | ||||
|     pub offset_x: usize, | ||||
|     pub offset_y: usize, | ||||
|     pub width: usize, | ||||
|     pub height: usize, | ||||
|     pub special: (usize, usize), | ||||
|     pub connection_down: Option<Connection>, | ||||
|     pub connection_right: Option<Connection>, | ||||
| } | ||||
| 
 | ||||
| impl Room { | ||||
|     pub fn new(rng: &mut ThreadRng) -> Self { | ||||
|         let width = rng.gen_range(ROOM_MIN_WIDTH..ROOM_WIDTH); | ||||
|         let height = rng.gen_range(ROOM_MIN_HEIGHT..ROOM_HEIGHT); | ||||
|         let offset_x = rng.gen_range(0..(ROOM_WIDTH - width)); | ||||
|         let offset_y = rng.gen_range(0..(ROOM_HEIGHT - height)); | ||||
|         let sx = offset_x + rng.gen_range(1..width - 1); | ||||
|         let sy = offset_y + rng.gen_range(1..height - 1); | ||||
| 
 | ||||
|         Self { | ||||
|             kind: RoomType::EmptyRoom, | ||||
|             offset_x, | ||||
|             offset_y, | ||||
|             width, | ||||
|             height, | ||||
|             special: (sx, sy), | ||||
|             connection_down: None, | ||||
|             connection_right: None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // pub fn get_x_range(&self) -> Range<usize> {
 | ||||
|     //     self.offset_x..self.offset_x + self.width
 | ||||
|     // }
 | ||||
| 
 | ||||
|     // pub fn get_y_range(&self) -> Range<usize> {
 | ||||
|     //     self.offset_y..self.offset_y + self.height
 | ||||
|     // }
 | ||||
| 
 | ||||
|     pub fn render( | ||||
|         &self, | ||||
|         tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH], | ||||
|         col: usize, | ||||
|         row: usize, | ||||
|     ) -> (usize, usize) { | ||||
|         let top = 1 + row * ROOM_HEIGHT; | ||||
|         let left = 1 + col * ROOM_WIDTH; | ||||
|         for col in tgt.iter_mut().skip(left).take(ROOM_WIDTH) { | ||||
|             //left..left + ROOM_WIDTH {
 | ||||
|             for row_element in col.iter_mut().skip(top).take(ROOM_HEIGHT) { | ||||
|                 // top..top + ROOM_HEIGHT {
 | ||||
|                 *row_element = StructureElement::Wall; | ||||
|             } | ||||
|         } | ||||
|         if self.kind == RoomType::EmptyRoom { | ||||
|             return (0, 0); | ||||
|         } | ||||
|         // render floor elements
 | ||||
|         for x in 0..self.width { | ||||
|             for y in 0..self.height { | ||||
|                 tgt[left + self.offset_x + x][top + self.offset_y + y] = StructureElement::Floor; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         match self.kind { | ||||
|             RoomType::Start => { | ||||
|                 tgt[left + self.special.0][top + self.special.1] = StructureElement::Start; | ||||
|             } | ||||
|             RoomType::End => { | ||||
|                 tgt[left + self.special.0][top + self.special.1] = StructureElement::End; | ||||
|             } | ||||
|             RoomType::StairUp => { | ||||
|                 tgt[left + self.special.0][top + self.special.1] = StructureElement::StairUp; | ||||
|             } | ||||
|             RoomType::StairDown => { | ||||
|                 tgt[left + self.special.0][top + self.special.1] = StructureElement::StairDown; | ||||
|             } | ||||
|             _ => {} | ||||
|         }; | ||||
|         (left + self.special.0, top + self.special.1) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_room_creation() { | ||||
|     let mut rng = rand::thread_rng(); | ||||
|     let room = Room::new(&mut rng); | ||||
|     assert_eq!(room.kind, RoomType::EmptyRoom); | ||||
|     for _ in 0..1000 { | ||||
|         let room = Room::new(&mut rng); | ||||
|         assert!((3..=ROOM_WIDTH).contains(&room.width)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| fn all_wall( | ||||
|     tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH], | ||||
|     col: usize, | ||||
|     row: usize, | ||||
| ) -> bool { | ||||
|     let top = 1 + row * ROOM_HEIGHT; | ||||
|     let left = 1 + col * ROOM_WIDTH; | ||||
|     for x in left..left + ROOM_WIDTH { | ||||
|         for y in top..top + ROOM_HEIGHT { | ||||
|             if tgt[x][y] != StructureElement::Wall { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     true | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| fn has_structure_element( | ||||
|     tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH], | ||||
|     col: usize, | ||||
|     row: usize, | ||||
|     element: StructureElement, | ||||
| ) -> bool { | ||||
|     let top = 1 + row * ROOM_HEIGHT; | ||||
|     let left = 1 + col * ROOM_WIDTH; | ||||
|     for x in left..left + ROOM_WIDTH { | ||||
|         for y in top..top + ROOM_HEIGHT { | ||||
|             if tgt[x][y] == element { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     false | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_room_render_empty() { | ||||
|     let mut rng = rand::thread_rng(); | ||||
|     let room = Room::new(&mut rng); | ||||
|     assert_eq!(room.kind, RoomType::EmptyRoom); | ||||
|     let mut structure = [[StructureElement::Floor; LEVEL_HEIGHT]; LEVEL_WIDTH]; | ||||
| 
 | ||||
|     room.render(&mut structure, 0, 0); | ||||
|     assert!(all_wall(&mut structure, 0, 0)); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_room_render_basic() { | ||||
|     let mut rng = rand::thread_rng(); | ||||
|     let mut room = Room::new(&mut rng); | ||||
|     room.kind = RoomType::BasicRoom; | ||||
|     let mut structure = [[StructureElement::Floor; LEVEL_HEIGHT]; LEVEL_WIDTH]; | ||||
| 
 | ||||
|     room.render(&mut structure, 1, 3); | ||||
|     assert!(!all_wall(&mut structure, 1, 3)); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_room_render_start() { | ||||
|     let mut rng = rand::thread_rng(); | ||||
|     let mut room = Room::new(&mut rng); | ||||
|     room.kind = RoomType::Start; | ||||
|     let mut structure = [[StructureElement::Floor; LEVEL_HEIGHT]; LEVEL_WIDTH]; | ||||
| 
 | ||||
|     room.render(&mut structure, 1, 3); | ||||
|     assert!(!all_wall(&mut structure, 1, 3)); | ||||
|     assert!(has_structure_element( | ||||
|         &mut structure, | ||||
|         1, | ||||
|         3, | ||||
|         StructureElement::Start | ||||
|     )); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user