package sprite import ( "image" "image/color" "strconv" "testing" ) func TestNamedSpriteBoard(t *testing.T) { // 创建测试板 board := NewNamedSpriteBoard() // 测试添加精灵 sprite1 := &Sprite{ Name: "sprite1", Position: image.Point{X: 10, Y: 10}, Images: []image.Image{image.NewRGBA(image.Rect(0, 0, 10, 10))}, Index: 5, } sprite2 := &Sprite{ Name: "sprite2", Position: image.Point{X: 20, Y: 20}, Images: []image.Image{image.NewRGBA(image.Rect(0, 0, 10, 10))}, Index: 3, } sprite3 := &Sprite{ Name: "sprite3", Position: image.Point{X: 30, Y: 30}, Images: []image.Image{image.NewRGBA(image.Rect(0, 0, 10, 10))}, Index: 8, } // 添加精灵 board.AddSprite(sprite1) board.AddSprite(sprite2) board.AddSprite(sprite3) // 验证总数量 if board.count != 3 { t.Errorf("期望精灵总数为3,实际为%d", board.count) } // 验证索引组数量 if len(board.indexMap) != 3 { t.Errorf("期望索引组数量为3,实际为%d", len(board.indexMap)) } // 验证索引组链表顺序 expectedIndices := []int{3, 5, 8} currentGroup := board.indexHead for i, expectedIndex := range expectedIndices { if currentGroup == nil { t.Errorf("索引组链表在位置%d处意外结束", i) break } if currentGroup.Index != expectedIndex { t.Errorf("索引组链表顺序错误,位置%d期望为%d,实际为%d", i, expectedIndex, currentGroup.Index) } currentGroup = currentGroup.Next } // 验证兼容层 if len(board.Sprites) != 3 { t.Errorf("兼容层数组长度错误,期望为3,实际为%d", len(board.Sprites)) } for i, expectedIndex := range expectedIndices { if board.Sprites[i].Index != expectedIndex { t.Errorf("兼容层数组索引顺序错误,位置%d期望为%d,实际为%d", i, expectedIndex, board.Sprites[i].Index) } } // 测试按名称查找 foundSprite := board.GetSpriteByName("sprite1") if foundSprite == nil || foundSprite.Index != 5 { t.Errorf("未能按名称找到精灵sprite1") } // 测试按索引查找 sprites := board.GetSpritesByIndex(5) if len(sprites) != 1 || sprites[0].Name != "sprite1" { t.Errorf("按索引查找精灵错误") } // 添加相同索引的精灵 sprite4 := &Sprite{ Name: "sprite4", Position: image.Point{X: 40, Y: 40}, Images: []image.Image{image.NewRGBA(image.Rect(0, 0, 10, 10))}, Index: 5, // 与sprite1相同的索引 } board.AddSprite(sprite4) // 验证总数量增加 if board.count != 4 { t.Errorf("添加相同索引精灵后,期望精灵总数为4,实际为%d", board.count) } // 验证索引组数量不变 if len(board.indexMap) != 3 { t.Errorf("添加相同索引精灵后,期望索引组数量为3,实际为%d", len(board.indexMap)) } // 验证索引5组内有两个精灵,且按名称排序 index5Group := board.indexMap[5] if index5Group.Count != 2 { t.Errorf("索引5组内精灵数量错误,期望为2,实际为%d", index5Group.Count) } // 验证索引5组内精灵按名称排序(sprite1应在sprite4之前) if index5Group.Head.Sprite.Name != "sprite1" || index5Group.Head.Next.Sprite.Name != "sprite4" { t.Errorf("索引5组内精灵顺序错误,期望为sprite1, sprite4,实际为%s, %s", index5Group.Head.Sprite.Name, index5Group.Head.Next.Sprite.Name) } // 验证按索引5查找返回两个精灵 sprites = board.GetSpritesByIndex(5) if len(sprites) != 2 { t.Errorf("按索引5查找应返回2个精灵,实际返回%d个", len(sprites)) } // 测试删除精灵 success := board.RemoveSpriteByName("sprite1") if !success { t.Errorf("删除精灵sprite1失败") } // 验证总数量减少 if board.count != 3 { t.Errorf("删除后期望精灵总数为3,实际为%d", board.count) } // 验证无法查找到已删除的精灵 foundSprite = board.GetSpriteByName("sprite1") if foundSprite != nil { t.Errorf("已删除的精灵仍可被查找到") } // 验证索引5组仍然存在且只有一个精灵 index5Group = board.indexMap[5] if index5Group == nil || index5Group.Count != 1 || index5Group.Head.Sprite.Name != "sprite4" { t.Errorf("删除sprite1后,索引5组状态错误") } // 测试更新精灵索引 success = board.UpdateSpriteIndex("sprite4", 10) if !success { t.Errorf("更新精灵索引失败") } // 验证索引已更新 foundSprite = board.GetSpriteByName("sprite4") if foundSprite == nil || foundSprite.Index != 10 { t.Errorf("精灵索引未成功更新") } // 验证原索引组被删除,新索引组已创建 _, exists := board.indexMap[5] if exists { t.Errorf("原索引5组未被删除") } _, exists = board.indexMap[10] if !exists { t.Errorf("新索引10组未被创建") } // 测试更新精灵名称 t.Logf("测试更新名称,当前精灵:%+v", *board.GetSpriteByName("sprite4")) success = board.UpdateSpriteName("sprite4", "sprite5") if !success { t.Errorf("更新精灵名称失败") } // 验证名称已更新 foundSprite = board.GetSpriteByName("sprite5") if foundSprite == nil { t.Errorf("无法用新名称找到精灵") } foundSprite = board.GetSpriteByName("sprite4") if foundSprite != nil { t.Errorf("仍能用旧名称找到精灵") } } // 性能测试 func BenchmarkNamedSpriteBoardAdd(b *testing.B) { board := NewNamedSpriteBoard() img := image.NewRGBA(image.Rect(0, 0, 10, 10)) b.ResetTimer() for i := 0; i < b.N; i++ { sprite := &Sprite{ Name: "sprite" + strconv.Itoa(i), Position: image.Point{X: i % 100, Y: i / 100}, Images: []image.Image{img}, Index: i % 100, // 使用100个不同的索引,模拟多个精灵共享索引 } board.AddSprite(sprite) } } func BenchmarkNamedSpriteBoardGetByName(b *testing.B) { // 准备测试数据 board := NewNamedSpriteBoard() img := image.NewRGBA(image.Rect(0, 0, 10, 10)) names := make([]string, 1000) for i := 0; i < 1000; i++ { name := "sprite" + strconv.Itoa(i) names[i] = name sprite := &Sprite{ Name: name, Position: image.Point{X: i % 100, Y: i / 100}, Images: []image.Image{img}, Index: i % 100, } board.AddSprite(sprite) } b.ResetTimer() for i := 0; i < b.N; i++ { board.GetSpriteByName(names[i%1000]) } } func BenchmarkNamedSpriteBoardGetByIndex(b *testing.B) { // 准备测试数据 board := NewNamedSpriteBoard() img := image.NewRGBA(image.Rect(0, 0, 10, 10)) for i := 0; i < 1000; i++ { sprite := &Sprite{ Name: "sprite" + strconv.Itoa(i), Position: image.Point{X: i % 100, Y: i / 100}, Images: []image.Image{img}, Index: i % 100, } board.AddSprite(sprite) } b.ResetTimer() for i := 0; i < b.N; i++ { board.GetSpritesByIndex(i % 100) } } func TestNamedSpriteBoardRenderToImage(t *testing.T) { // 创建测试板 board := NewNamedSpriteBoard() // 创建几个具有不同索引的彩色精灵 // 索引值小的精灵会被先绘制,索引值大的精灵会覆盖在上面 // 创建红色精灵(索引1,会被绘制在最底层) redImg := image.NewRGBA(image.Rect(0, 0, 50, 50)) for y := 0; y < 50; y++ { for x := 0; x < 50; x++ { redImg.Set(x, y, color.RGBA{255, 0, 0, 255}) // 红色 } } redSprite := &Sprite{ Name: "red", Position: image.Point{X: 50, Y: 50}, Images: []image.Image{redImg}, Index: 1, } // 创建绿色精灵(索引2,会覆盖红色精灵) greenImg := image.NewRGBA(image.Rect(0, 0, 50, 50)) for y := 0; y < 50; y++ { for x := 0; x < 50; x++ { greenImg.Set(x, y, color.RGBA{0, 255, 0, 255}) // 绿色 } } greenSprite := &Sprite{ Name: "green", Position: image.Point{X: 75, Y: 75}, Images: []image.Image{greenImg}, Index: 2, } // 创建蓝色精灵(索引2,与绿色相同索引,按名称排序在绿色之前) blueImg := image.NewRGBA(image.Rect(0, 0, 50, 50)) for y := 0; y < 50; y++ { for x := 0; x < 50; x++ { blueImg.Set(x, y, color.RGBA{0, 0, 255, 255}) // 蓝色 } } blueSprite := &Sprite{ Name: "blue", Position: image.Point{X: 85, Y: 85}, Images: []image.Image{blueImg}, Index: 2, } // 将精灵添加到精灵板(添加顺序混乱,但应按index和name排序渲染) board.AddSprite(greenSprite) board.AddSprite(redSprite) board.AddSprite(blueSprite) // 验证精灵被正确添加 if board.count != 3 { t.Errorf("期望精灵数量为3,实际为%d", board.count) } // 验证索引组数量 if len(board.indexMap) != 2 { t.Errorf("期望索引组数量为2,实际为%d", len(board.indexMap)) } // 验证索引为2的组内有两个精灵 index2Group := board.indexMap[2] if index2Group.Count != 2 { t.Errorf("索引2组内精灵数量错误,期望为2,实际为%d", index2Group.Count) } // 验证索引为2的组内精灵按名称排序(blue应在green之前) if index2Group.Head.Sprite.Name != "blue" || index2Group.Head.Next.Sprite.Name != "green" { t.Errorf("索引2组内精灵顺序错误,期望为blue, green,实际为%s, %s", index2Group.Head.Sprite.Name, index2Group.Head.Next.Sprite.Name) } // 渲染图像 img := board.RenderToImage() // 验证图像大小 if img.Bounds().Dx() != 200 || img.Bounds().Dy() != 200 { t.Errorf("渲染图像大小错误,期望200x200,实际%dx%d", img.Bounds().Dx(), img.Bounds().Dy()) } // 将saveImage设置为true查看实际渲染效果以调试 saveImage := false if saveImage { err := board.SaveToPng("test_render.png") if err != nil { t.Errorf("保存图像失败: %v", err) } t.Log("图像已保存至 test_render.png") } // 检查渲染顺序: // 索引排序:索引小的先渲染,后渲染的会覆盖先渲染的 // 在同一索引中,按名称字母顺序排序:blue在green之前渲染 // 检查红色精灵位置(索引1最小,最底层) redPos := image.Point{X: 60, Y: 60} redColor := img.At(redPos.X, redPos.Y) r, g, b, _ := redColor.RGBA() if r>>8 != 255 || g>>8 != 0 || b>>8 != 0 { t.Errorf("位置(%d,%d)颜色错误,期望为红色(255,0,0),实际为(%d,%d,%d)", redPos.X, redPos.Y, r>>8, g>>8, b>>8) } // 检查绿色特有区域 greenPos := image.Point{X: 120, Y: 120} greenColor := img.At(greenPos.X, greenPos.Y) r, g, b, _ = greenColor.RGBA() if r>>8 != 0 || g>>8 != 255 || b>>8 != 0 { t.Errorf("位置(%d,%d)颜色错误,期望为绿色(0,255,0),实际为(%d,%d,%d)", greenPos.X, greenPos.Y, r>>8, g>>8, b>>8) } // 检查蓝色特有区域 bluePos := image.Point{X: 130, Y: 130} blueColor := img.At(bluePos.X, bluePos.Y) r, g, b, _ = blueColor.RGBA() if r>>8 != 0 || g>>8 != 0 || b>>8 != 255 { t.Errorf("位置(%d,%d)颜色错误,期望为蓝色(0,0,255),实际为(%d,%d,%d)", bluePos.X, bluePos.Y, r>>8, g>>8, b>>8) } // 检查重叠区域(按实际绘制顺序) // 记住绘制的顺序:red(索引1) -> blue(索引2) -> green(索引2) // 所以重叠区域应该是最后绘制的颜色 overlapPos := image.Point{X: 90, Y: 90} overlapColor := img.At(overlapPos.X, overlapPos.Y) r, g, b, _ = overlapColor.RGBA() // 即使blue按名称排序在前(b < g),但按添加顺序渲染后,green会覆盖blue // 按正确的排序,索引大的排后面,同索引按名称排序 if r>>8 != 0 || g>>8 != 255 || b>>8 != 0 { t.Errorf("重叠位置(%d,%d)颜色错误,期望为绿色(0,255,0),实际为(%d,%d,%d)", overlapPos.X, overlapPos.Y, r>>8, g>>8, b>>8) } }