imagedd/sprite/named_sprite_board_test.go

388 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}