#include "test_dungeon_objects.h" #include "mocks/mock_rom.h" #include "app/zelda3/dungeon/object_parser.h" #include "app/zelda3/dungeon/object_renderer.h" #include "app/zelda3/dungeon/room_object.h" #include "app/zelda3/dungeon/room_layout.h" #include "app/gfx/snes_color.h" #include "app/gfx/snes_palette.h" #include "testing.h" #include #include #include "gtest/gtest.h" namespace yaze { namespace test { void TestDungeonObjects::SetUp() { test_rom_ = std::make_unique(); ASSERT_TRUE(CreateTestRom().ok()); ASSERT_TRUE(SetupObjectData().ok()); } void TestDungeonObjects::TearDown() { test_rom_.reset(); } absl::Status TestDungeonObjects::CreateTestRom() { // Create basic ROM data std::vector rom_data(kTestRomSize, 0x00); // Set up ROM header std::string title = "ZELDA3 TEST"; std::memcpy(&rom_data[0x7FC0], title.c_str(), std::min(title.length(), size_t(21))); rom_data[0x7FD7] = 0x21; // 2MB ROM // Set up object tables auto subtype1_table = CreateObjectSubtypeTable(0x8000, 0x100); auto subtype2_table = CreateObjectSubtypeTable(0x83F0, 0x80); auto subtype3_table = CreateObjectSubtypeTable(0x84F0, 0x100); // Copy tables to ROM data std::copy(subtype1_table.begin(), subtype1_table.end(), rom_data.begin() + 0x8000); std::copy(subtype2_table.begin(), subtype2_table.end(), rom_data.begin() + 0x83F0); std::copy(subtype3_table.begin(), subtype3_table.end(), rom_data.begin() + 0x84F0); // Set up tile data auto tile_data = CreateTileData(0x1B52, 0x400); std::copy(tile_data.begin(), tile_data.end(), rom_data.begin() + 0x1B52); return test_rom_->SetTestData(rom_data); } absl::Status TestDungeonObjects::SetupObjectData() { // Set up test object data std::vector object_data = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; test_rom_->SetObjectData(kTestObjectId, object_data); // Set up test room data auto room_header = CreateRoomHeader(kTestRoomId); test_rom_->SetRoomData(kTestRoomId, room_header); return absl::OkStatus(); } std::vector TestDungeonObjects::CreateObjectSubtypeTable(int base_addr, int count) { std::vector table(count * 2, 0x00); for (int i = 0; i < count; i++) { int addr = i * 2; // Point to tile data at 0x1B52 + (i * 8) int tile_offset = (i * 8) & 0xFFFF; table[addr] = tile_offset & 0xFF; table[addr + 1] = (tile_offset >> 8) & 0xFF; } return table; } std::vector TestDungeonObjects::CreateTileData(int base_addr, int tile_count) { std::vector data(tile_count * 8, 0x00); for (int i = 0; i < tile_count; i++) { int addr = i * 8; // Create simple tile data for (int j = 0; j < 8; j++) { data[addr + j] = (i + j) & 0xFF; } } return data; } std::vector TestDungeonObjects::CreateRoomHeader(int room_id) { std::vector header(32, 0x00); // Basic room properties header[0] = 0x00; // Background type, collision, light header[1] = 0x00; // Palette header[2] = 0x01; // Blockset header[3] = 0x01; // Spriteset header[4] = 0x00; // Effect header[5] = 0x00; // Tag1 header[6] = 0x00; // Tag2 return header; } // Test cases TEST_F(TestDungeonObjects, ObjectParserBasicTest) { zelda3::ObjectParser parser(test_rom_.get()); auto result = parser.ParseObject(kTestObjectId); ASSERT_TRUE(result.ok()); EXPECT_FALSE(result->empty()); } TEST_F(TestDungeonObjects, ObjectRendererBasicTest) { zelda3::ObjectRenderer renderer(test_rom_.get()); // Create test object auto room_object = zelda3::RoomObject(kTestObjectId, 0, 0, 0x12, 0); room_object.set_rom(test_rom_.get()); room_object.EnsureTilesLoaded(); // Create test palette gfx::SnesPalette palette; for (int i = 0; i < 16; i++) { palette.AddColor(gfx::SnesColor(i * 16, i * 16, i * 16)); } auto result = renderer.RenderObject(room_object, palette); ASSERT_TRUE(result.ok()); EXPECT_GT(result->width(), 0); EXPECT_GT(result->height(), 0); } TEST_F(TestDungeonObjects, RoomObjectTileLoadingTest) { auto room_object = zelda3::RoomObject(kTestObjectId, 5, 5, 0x12, 0); room_object.set_rom(test_rom_.get()); // Test tile loading room_object.EnsureTilesLoaded(); EXPECT_FALSE(room_object.tiles().empty()); } TEST_F(TestDungeonObjects, MockRomDataTest) { auto* mock_rom = static_cast(test_rom_.get()); EXPECT_TRUE(mock_rom->HasObjectData(kTestObjectId)); EXPECT_TRUE(mock_rom->HasRoomData(kTestRoomId)); EXPECT_TRUE(mock_rom->IsValid()); } TEST_F(TestDungeonObjects, RoomObjectTileAccessTest) { auto room_object = zelda3::RoomObject(kTestObjectId, 5, 5, 0x12, 0); room_object.set_rom(test_rom_.get()); room_object.EnsureTilesLoaded(); // Test new tile access methods auto tiles_result = room_object.GetTiles(); EXPECT_TRUE(tiles_result.ok()); if (tiles_result.ok()) { EXPECT_FALSE(tiles_result->empty()); } // Test individual tile access auto tile_result = room_object.GetTile(0); EXPECT_TRUE(tile_result.ok()); if (tile_result.ok()) { const auto* tile = tile_result.value(); EXPECT_NE(tile, nullptr); } // Test tile count EXPECT_GT(room_object.GetTileCount(), 0); // Test out of range access auto bad_tile_result = room_object.GetTile(999); EXPECT_FALSE(bad_tile_result.ok()); } TEST_F(TestDungeonObjects, ObjectRendererGraphicsSheetTest) { zelda3::ObjectRenderer renderer(test_rom_.get()); // Create test object auto room_object = zelda3::RoomObject(kTestObjectId, 0, 0, 0x12, 0); room_object.set_rom(test_rom_.get()); room_object.EnsureTilesLoaded(); // Create test palette gfx::SnesPalette palette; for (int i = 0; i < 16; i++) { palette.AddColor(gfx::SnesColor(i * 16, i * 16, i * 16)); } // Test rendering with graphics sheet lookup auto result = renderer.RenderObject(room_object, palette); ASSERT_TRUE(result.ok()); auto bitmap = std::move(result.value()); EXPECT_TRUE(bitmap.is_active()); EXPECT_NE(bitmap.surface(), nullptr); EXPECT_GT(bitmap.width(), 0); EXPECT_GT(bitmap.height(), 0); } TEST_F(TestDungeonObjects, BitmapCopySemanticsTest) { // Test bitmap copying works correctly std::vector data(32 * 32, 0x42); gfx::Bitmap original(32, 32, 8, data); // Test copy constructor gfx::Bitmap copy = original; EXPECT_EQ(copy.width(), original.width()); EXPECT_EQ(copy.height(), original.height()); EXPECT_TRUE(copy.is_active()); EXPECT_NE(copy.surface(), nullptr); // Test copy assignment gfx::Bitmap assigned; assigned = original; EXPECT_EQ(assigned.width(), original.width()); EXPECT_EQ(assigned.height(), original.height()); EXPECT_TRUE(assigned.is_active()); EXPECT_NE(assigned.surface(), nullptr); } TEST_F(TestDungeonObjects, BitmapMoveSemanticsTest) { // Test bitmap moving works correctly std::vector data(32 * 32, 0x42); gfx::Bitmap original(32, 32, 8, data); // Test move constructor gfx::Bitmap moved = std::move(original); EXPECT_EQ(moved.width(), 32); EXPECT_EQ(moved.height(), 32); EXPECT_TRUE(moved.is_active()); EXPECT_NE(moved.surface(), nullptr); // Original should be in a valid but empty state EXPECT_EQ(original.width(), 0); EXPECT_EQ(original.height(), 0); EXPECT_FALSE(original.is_active()); EXPECT_EQ(original.surface(), nullptr); } TEST_F(TestDungeonObjects, PaletteHandlingTest) { // Test palette handling and hash calculation gfx::SnesPalette palette; for (int i = 0; i < 16; i++) { palette.AddColor(gfx::SnesColor(i * 16, i * 16, i * 16)); } EXPECT_EQ(palette.size(), 16); // Test palette hash calculation (used in caching) uint64_t hash1 = 0; for (size_t i = 0; i < palette.size(); ++i) { hash1 ^= std::hash{}(palette[i].snes()) + 0x9e3779b9 + (hash1 << 6) + (hash1 >> 2); } // Same palette should produce same hash uint64_t hash2 = 0; for (size_t i = 0; i < palette.size(); ++i) { hash2 ^= std::hash{}(palette[i].snes()) + 0x9e3779b9 + (hash2 << 6) + (hash2 >> 2); } EXPECT_EQ(hash1, hash2); EXPECT_NE(hash1, 0); // Hash should not be zero } TEST_F(TestDungeonObjects, ObjectSizeCalculationTest) { zelda3::ObjectParser parser(test_rom_.get()); // Test object size parsing auto size_result = parser.ParseObjectSize(0x01, 0x12); EXPECT_TRUE(size_result.ok()); if (size_result.ok()) { const auto& size_info = size_result.value(); EXPECT_GT(size_info.width_tiles, 0); EXPECT_GT(size_info.height_tiles, 0); EXPECT_TRUE(size_info.is_repeatable); } } TEST_F(TestDungeonObjects, ObjectSubtypeDeterminationTest) { zelda3::ObjectParser parser(test_rom_.get()); // Test subtype determination EXPECT_EQ(parser.DetermineSubtype(0x01), 1); EXPECT_EQ(parser.DetermineSubtype(0x100), 2); EXPECT_EQ(parser.DetermineSubtype(0x200), 3); // Test object subtype info auto subtype_result = parser.GetObjectSubtype(0x01); EXPECT_TRUE(subtype_result.ok()); if (subtype_result.ok()) { EXPECT_EQ(subtype_result->subtype, 1); EXPECT_GT(subtype_result->max_tile_count, 0); } } TEST_F(TestDungeonObjects, RoomLayoutObjectCreationTest) { zelda3::RoomLayoutObject obj(0x01, 5, 10, zelda3::RoomLayoutObject::Type::kWall, 0); EXPECT_EQ(obj.id(), 0x01); EXPECT_EQ(obj.x(), 5); EXPECT_EQ(obj.y(), 10); EXPECT_EQ(obj.type(), zelda3::RoomLayoutObject::Type::kWall); EXPECT_EQ(obj.layer(), 0); // Test type name EXPECT_EQ(obj.GetTypeName(), "Wall"); // Test tile creation auto tile_result = obj.GetTile(); EXPECT_TRUE(tile_result.ok()); } TEST_F(TestDungeonObjects, RoomLayoutLoadingTest) { zelda3::RoomLayout layout(test_rom_.get()); // Test loading layout for room 0 auto status = layout.LoadLayout(0); // This might fail due to missing layout data, which is expected // We're testing that the method doesn't crash // Test getting objects by type auto walls = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kWall); auto floors = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kFloor); // Test dimensions auto [width, height] = layout.GetDimensions(); EXPECT_GT(width, 0); EXPECT_GT(height, 0); // Test object access auto obj_result = layout.GetObjectAt(0, 0, 0); // This might fail if no object exists at that position, which is expected } TEST_F(TestDungeonObjects, RoomLayoutCollisionTest) { zelda3::RoomLayout layout(test_rom_.get()); // Test collision detection methods EXPECT_FALSE(layout.HasWall(0, 0, 0)); // Should be false for empty layout EXPECT_FALSE(layout.HasFloor(0, 0, 0)); // Should be false for empty layout // Test with a simple layout std::vector layout_data = { 0x01, 0x01, 0x00, 0x00, // Wall, Wall, Empty, Empty 0x21, 0x21, 0x21, 0x21, // Floor, Floor, Floor, Floor 0x00, 0x00, 0x00, 0x00, // Empty, Empty, Empty, Empty }; // This would require the layout to be properly set up // For now, we just test that the methods don't crash } } // namespace test } // namespace yaze