388 lines
11 KiB
Go
388 lines
11 KiB
Go
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)
|
||
}
|
||
}
|