From 5f83823a2321f8360850d860e11b7ee199950fa4 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Sat, 1 Mar 2025 23:58:05 +1000 Subject: [PATCH 01/14] Tests CI the works --- ...ild-release.yml => test-build-release.yml} | 105 +++++++---- TODO.md | 1 + src/components/inventory.rs | 169 ++++++++++++++++++ 3 files changed, 242 insertions(+), 33 deletions(-) rename .github/workflows/{build-release.yml => test-build-release.yml} (58%) diff --git a/.github/workflows/build-release.yml b/.github/workflows/test-build-release.yml similarity index 58% rename from .github/workflows/build-release.yml rename to .github/workflows/test-build-release.yml index 2b39afd..bb4d783 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/test-build-release.yml @@ -1,23 +1,51 @@ -name: Build and Release +name: Test, Build and Release on: push: - tags: - - 'v*' # Trigger on tags like v0.1.0 + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + - cron: '0 0 * * *' # Run every day at midnight UTC for nightly builds workflow_dispatch: # Allow manual trigger jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y libasound2-dev libudev-dev pkg-config + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + + - name: Run tests + run: cargo test + + - name: Run clippy + run: cargo clippy -- -D warnings + build-windows: + name: Build Windows + needs: test # This ensures tests must pass before build starts runs-on: windows-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + uses: dtolnay/rust-toolchain@stable - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 @@ -29,20 +57,37 @@ jobs: run: | mkdir -p dist/assets copy target\release\my-rts-game.exe dist\ - xcopy assets dist\assets\ /E /I /Y - copy README.md dist\ 2>NUL || echo No README found + if (Test-Path -Path assets) { xcopy assets dist\assets\ /E /I /Y } + if (Test-Path -Path README.md) { copy README.md dist\ } + + - name: Generate timestamp + id: timestamp + run: echo "::set-output name=value::$(Get-Date -Format 'yyyyMMdd-HHmmss')" + shell: pwsh + + - name: Create ZIP Archive + run: | + Compress-Archive -Path dist\* -DestinationPath my-rts-game-nightly-${{ steps.timestamp.outputs.value }}.zip + shell: pwsh + + - name: Upload ZIP + uses: actions/upload-artifact@v3 + with: + name: windows-zip + path: my-rts-game-nightly-${{ steps.timestamp.outputs.value }}.zip - name: Download Inno Setup run: | Invoke-WebRequest -Uri "https://files.jrsoftware.org/is/6/innosetup-6.2.2.exe" -OutFile innosetup.exe Start-Process -FilePath "innosetup.exe" -ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-" -Wait + shell: pwsh - name: Create Inno Setup Script run: | - $VERSION = "${{ github.ref_name }}" -replace "v", "" - cat > installer.iss << EOF + $timestamp = "${{ steps.timestamp.outputs.value }}" + @" #define MyAppName "My RTS Game" - #define MyAppVersion "$VERSION" + #define MyAppVersion "nightly-$timestamp" #define MyAppPublisher "Your Name" #define MyAppURL "https://github.com/${{ github.repository }}" #define MyAppExeName "my-rts-game.exe" @@ -78,7 +123,7 @@ jobs: [Run] Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent - EOF + "@ | Out-File -FilePath installer.iss -Encoding utf8 shell: pwsh - name: Compile Installer @@ -86,38 +131,32 @@ jobs: & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" installer.iss shell: pwsh - - name: Create ZIP Archive - run: | - Compress-Archive -Path dist\* -DestinationPath my-rts-game-${{ github.ref_name }}.zip - shell: pwsh - - name: Upload Installer - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: windows-installer path: installer/*.exe - - name: Upload ZIP - uses: actions/upload-artifact@v4 - with: - name: windows-zip - path: my-rts-game-${{ github.ref_name }}.zip - - release: + create-release: + name: Create Release needs: build-windows + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') steps: + - name: Generate timestamp for release name + id: timestamp + run: echo "value=$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_OUTPUT + - name: Download all artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v3 - - name: Create Release - id: create_release - uses: softprops/action-gh-release@v2 + - name: Create Nightly Release + uses: softprops/action-gh-release@v1 with: + name: "Nightly Build ${{ steps.timestamp.outputs.value }}" + tag_name: "nightly-${{ steps.timestamp.outputs.value }}" files: | windows-installer/*.exe windows-zip/*.zip - draft: false - prerelease: false + prerelease: true generate_release_notes: true diff --git a/TODO.md b/TODO.md index 03df5ef..a4c935e 100644 --- a/TODO.md +++ b/TODO.md @@ -5,6 +5,7 @@ - [x] Fix worker movement bug - prevent workers from drifting downward after filling their inventory - [x] Build an exe file for Windows - [x] Add a dependabot setup +- [ ] Add tests - [ ] Add an icon for the game - [ ] Add a splash screen - [ ] Add a main menu diff --git a/src/components/inventory.rs b/src/components/inventory.rs index b291982..6d65f88 100644 --- a/src/components/inventory.rs +++ b/src/components/inventory.rs @@ -85,3 +85,172 @@ impl Inventory { &self.resources } } + +// Replace the current tests module with this more comprehensive version + +#[cfg(test)] +mod tests { + use super::*; + use crate::resources::ResourceId; + + #[test] + fn test_inventory_new() { + let inventory = Inventory::new(20); + assert_eq!(inventory.capacity(), 20); + assert_eq!(inventory.used_capacity(), 0); + assert!(inventory.is_empty()); + assert!(inventory.resources().is_empty()); + } + + #[test] + fn test_inventory_capacity() { + let inventory = Inventory::new(20); + assert_eq!(inventory.capacity(), 20); + } + + #[test] + fn test_inventory_is_full() { + let mut inventory = Inventory::new(10); + let resource_id = ResourceId("gold".to_string()); + + // Initially empty + assert!(!inventory.is_full()); + + // Add resources until full + inventory.add(&resource_id, 10); + assert!(inventory.is_full()); + } + + #[test] + fn test_inventory_is_empty() { + let mut inventory = Inventory::new(10); + let gold = ResourceId("gold".to_string()); + + // Initially empty + assert!(inventory.is_empty()); + + // Add some resources + inventory.add(&gold, 5); + assert!(!inventory.is_empty()); + + // Remove all resources + inventory.remove(&gold, 5); + assert!(inventory.is_empty()); + } + + #[test] + fn test_inventory_used_capacity() { + let mut inventory = Inventory::new(30); + let gold = ResourceId("gold".to_string()); + let wood = ResourceId("wood".to_string()); + + assert_eq!(inventory.used_capacity(), 0); + + inventory.add(&gold, 10); + assert_eq!(inventory.used_capacity(), 10); + + inventory.add(&wood, 15); + assert_eq!(inventory.used_capacity(), 25); + + inventory.remove(&gold, 5); + assert_eq!(inventory.used_capacity(), 20); + } + + #[test] + fn test_inventory_add_remove() { + let mut inventory = Inventory::new(30); + let gold = ResourceId("gold".to_string()); + let wood = ResourceId("wood".to_string()); + + inventory.add(&gold, 10); + inventory.add(&wood, 5); + + assert_eq!(inventory.get_amount(&gold), 10); + assert_eq!(inventory.get_amount(&wood), 5); + + inventory.remove(&gold, 3); + assert_eq!(inventory.get_amount(&gold), 7); + + // Test removing more than available + inventory.remove(&wood, 10); + assert_eq!(inventory.get_amount(&wood), 0); + } + + #[test] + fn test_inventory_add_beyond_capacity() { + let mut inventory = Inventory::new(10); + let gold = ResourceId("gold".to_string()); + + // Try to add more than capacity + let added = inventory.add(&gold, 15); + + // Should only add up to capacity + assert_eq!(added, 10); + assert_eq!(inventory.get_amount(&gold), 10); + assert_eq!(inventory.used_capacity(), 10); + assert!(inventory.is_full()); + } + + #[test] + fn test_inventory_add_to_existing() { + let mut inventory = Inventory::new(20); + let gold = ResourceId("gold".to_string()); + + inventory.add(&gold, 5); + assert_eq!(inventory.get_amount(&gold), 5); + + inventory.add(&gold, 3); + assert_eq!(inventory.get_amount(&gold), 8); + } + + #[test] + fn test_inventory_remove_from_empty() { + let mut inventory = Inventory::new(20); + let gold = ResourceId("gold".to_string()); + + // Try to remove from empty + let removed = inventory.remove(&gold, 5); + + // Should remove nothing + assert_eq!(removed, 0); + assert_eq!(inventory.used_capacity(), 0); + } + + #[test] + fn test_inventory_resources_map() { + let mut inventory = Inventory::new(30); + let gold = ResourceId("gold".to_string()); + let wood = ResourceId("wood".to_string()); + + inventory.add(&gold, 10); + inventory.add(&wood, 5); + + let resources = inventory.resources(); + assert_eq!(resources.len(), 2); + assert!(resources.contains_key(&gold)); + assert!(resources.contains_key(&wood)); + assert_eq!(*resources.get(&gold).unwrap(), 10); + assert_eq!(*resources.get(&wood).unwrap(), 5); + } + + #[test] + fn test_inventory_get_nonexistent() { + let inventory = Inventory::new(10); + let nonexistent = ResourceId("nonexistent".to_string()); + + // Should return 0 for nonexistent resources + assert_eq!(inventory.get_amount(&nonexistent), 0); + } + + #[test] + fn test_inventory_remove_all_removes_entry() { + let mut inventory = Inventory::new(20); + let gold = ResourceId("gold".to_string()); + + inventory.add(&gold, 5); + assert!(inventory.resources().contains_key(&gold)); + + inventory.remove(&gold, 5); // Remove all + assert!(!inventory.resources().contains_key(&gold)); // Entry should be gone + } +} From 1038560d35c67ed8634ca411d1bb9ac0aff2082a Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Sun, 2 Mar 2025 00:03:52 +1000 Subject: [PATCH 02/14] Update the plan --- TODO.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index a4c935e..349b5bb 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,8 @@ - [x] Fix worker movement bug - prevent workers from drifting downward after filling their inventory - [x] Build an exe file for Windows - [x] Add a dependabot setup -- [ ] Add tests +- [x] Setup test framework +- [ ] Add complete unit tests for each .rs file - [ ] Add an icon for the game - [ ] Add a splash screen - [ ] Add a main menu From 7af2e8fe1db6b8dd6d6777c40d7510d0e77a7436 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Sun, 2 Mar 2025 00:23:41 +1000 Subject: [PATCH 03/14] refactor: simplify resource and selection systems by introducing type aliases for ParamSet --- src/resources.rs | 10 +--------- src/systems/gathering.rs | 14 +++++++++----- src/systems/selection.rs | 10 ++++++---- src/systems/ui.rs | 12 +++++++----- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/resources.rs b/src/resources.rs index 2fd4879..af6e6cc 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -114,16 +114,8 @@ impl PlayerResources { } // Game state (keep this if you need it) -#[derive(Resource)] +#[derive(Resource, Default)] pub struct GameState { #[allow(dead_code)] pub paused: bool, // Will be used for pause functionality } - -impl Default for GameState { - fn default() -> Self { - GameState { - paused: false, - } - } -} diff --git a/src/systems/gathering.rs b/src/systems/gathering.rs index 44bd99f..1eab440 100644 --- a/src/systems/gathering.rs +++ b/src/systems/gathering.rs @@ -12,6 +12,13 @@ use crate::systems::animation::{GatherEffect, FloatingText}; // Add this import at the top use crate::components::inventory::Inventory; +// Add at the top of the file near other type definitions + +type GatheringParamSet = ParamSet<( + Query<(Entity, &mut Gathering, &mut Transform, &mut Velocity, &mut Inventory)>, + Query<(Entity, &mut ResourceNode, &Transform)> +)>; + // This system handles right-click on resources to start gathering pub fn resource_gathering_command( mut commands: Commands, @@ -81,10 +88,7 @@ pub fn gathering_system( time: Res