refactor: 收口 BangDream 难度列表规格
This commit is contained in:
parent
b360a005e7
commit
ef0f571ae8
@ -388,6 +388,7 @@ export interface TsuguHook {
|
|||||||
- 已新增 `render-blocks/list-player-card-icon-spec.ts`,收口玩家详情主卡组展示顺序、默认行高、文本字号比例、卡牌间距比例和卡牌图标可见性标记;`list-player-card-icon-list.ts` 改为消费 spec,主卡组渲染顺序和输出结构保持不变;`list-player-card-icon-spec.spec.ts` 覆盖历史卡牌顺序、缺失条目跳过和字号/间距计算,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
|
- 已新增 `render-blocks/list-player-card-icon-spec.ts`,收口玩家详情主卡组展示顺序、默认行高、文本字号比例、卡牌间距比例和卡牌图标可见性标记;`list-player-card-icon-list.ts` 改为消费 spec,主卡组渲染顺序和输出结构保持不变;`list-player-card-icon-spec.spec.ts` 覆盖历史卡牌顺序、缺失条目跳过和字号/间距计算,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
|
||||||
- 已新增 `render-blocks/list-card-icon-spec.ts`,收口通用卡牌图标列表默认行高、文本字号比例、卡牌间距比例、默认可见性标记和历史排序比较器;`list-card-icon-list.ts` 改为消费 spec,卡牌图标列表排序和输出结构保持不变;`list-card-icon-spec.spec.ts` 覆盖稀有度排序、优先卡牌类型排序、普通卡按 ID 排序和字号/间距计算,本地 `/查卡 472` 图片 smoke 输出非空。
|
- 已新增 `render-blocks/list-card-icon-spec.ts`,收口通用卡牌图标列表默认行高、文本字号比例、卡牌间距比例、默认可见性标记和历史排序比较器;`list-card-icon-list.ts` 改为消费 spec,卡牌图标列表排序和输出结构保持不变;`list-card-icon-spec.spec.ts` 覆盖稀有度排序、优先卡牌类型排序、普通卡按 ID 排序和字号/间距计算,本地 `/查卡 472` 图片 smoke 输出非空。
|
||||||
- 已新增 `render-blocks/list-song-spec.ts`,收口歌曲列表单行封面、文本、难度块位置、列表内容宽度、分割线高度和外层行高计算;`list-song.ts` 改为消费 spec,歌曲列表绘制顺序和输出结构保持不变;`list-song-spec.spec.ts` 覆盖单行尺寸、难度块垂直居中和歌曲组列表尺寸,本地 `/查曲 136` 图片 smoke 输出非空。
|
- 已新增 `render-blocks/list-song-spec.ts`,收口歌曲列表单行封面、文本、难度块位置、列表内容宽度、分割线高度和外层行高计算;`list-song.ts` 改为消费 spec,歌曲列表绘制顺序和输出结构保持不变;`list-song-spec.spec.ts` 覆盖单行尺寸、难度块垂直居中和歌曲组列表尺寸,本地 `/查曲 136` 图片 smoke 输出非空。
|
||||||
|
- 已新增 `render-blocks/list-difficulty-spec.ts`,收口难度列表默认高度/间距、徽章兜底色、圆形布局和等级文字比例/居中计算;`list-difficulty.ts` 改为消费 spec,难度列表绘制顺序和输出结构保持不变;`list-difficulty-spec.spec.ts` 覆盖列表宽度、条目偏移、徽章颜色、圆形布局和文字居中,本地 `/查曲 136` 图片 smoke 输出非空。
|
||||||
- smoke/Jenkins/远程调试卡点继续按已固化规则处理:同一卡点第二次尝试前必须改变可验证变量;Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home;线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录;PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值;smoke 没有真实图片落盘时必须失败。
|
- smoke/Jenkins/远程调试卡点继续按已固化规则处理:同一卡点第二次尝试前必须改变可验证变量;Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home;线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录;PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值;smoke 没有真实图片落盘时必须失败。
|
||||||
|
|
||||||
### Phase 6:策略 policy 和时间/档线规则
|
### Phase 6:策略 policy 和时间/档线规则
|
||||||
|
|||||||
@ -0,0 +1,116 @@
|
|||||||
|
interface ImageLike {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BANGDREAM_DIFFICULTY_LIST_SPEC = {
|
||||||
|
badge: {
|
||||||
|
arcStart: 0,
|
||||||
|
fallbackColor: '#f1f1f1',
|
||||||
|
fullCircleRadian: Math.PI * 2,
|
||||||
|
textMaxWidthRatio: 3,
|
||||||
|
textSizeRatio: 2 / 3,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
defaultImageHeight: 60,
|
||||||
|
defaultSpacing: 10,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算难度列表画布宽度。
|
||||||
|
*
|
||||||
|
* @param difficultyCount - 难度数量。
|
||||||
|
* @param imageHeight - 单个难度徽章高度。
|
||||||
|
* @param spacing - 难度徽章间距。
|
||||||
|
*/
|
||||||
|
export function getDifficultyListCanvasWidth(
|
||||||
|
difficultyCount: number,
|
||||||
|
imageHeight: number,
|
||||||
|
spacing: number,
|
||||||
|
) {
|
||||||
|
return imageHeight * difficultyCount + (difficultyCount - 1) * spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算难度徽章在列表中的横向位置。
|
||||||
|
*
|
||||||
|
* @param index - 难度下标。
|
||||||
|
* @param imageHeight - 单个难度徽章高度。
|
||||||
|
* @param spacing - 难度徽章间距。
|
||||||
|
*/
|
||||||
|
export function getDifficultyListItemX(
|
||||||
|
index: number,
|
||||||
|
imageHeight: number,
|
||||||
|
spacing: number,
|
||||||
|
) {
|
||||||
|
return index * (imageHeight + spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取难度徽章颜色。
|
||||||
|
*
|
||||||
|
* @param difficultyType - 难度类型。
|
||||||
|
* @param colors - 难度颜色表。
|
||||||
|
*/
|
||||||
|
export function getDifficultyBadgeColor(
|
||||||
|
difficultyType: number,
|
||||||
|
colors: ReadonlyArray<string | undefined>,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
colors[difficultyType] ??
|
||||||
|
BANGDREAM_DIFFICULTY_LIST_SPEC.badge.fallbackColor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算难度徽章圆形布局。
|
||||||
|
*
|
||||||
|
* @param imageHeight - 难度徽章高度。
|
||||||
|
*/
|
||||||
|
export function createDifficultyBadgeLayout(imageHeight: number) {
|
||||||
|
return {
|
||||||
|
arcEnd: BANGDREAM_DIFFICULTY_LIST_SPEC.badge.fullCircleRadian,
|
||||||
|
arcRadius: imageHeight / 2,
|
||||||
|
arcStart: BANGDREAM_DIFFICULTY_LIST_SPEC.badge.arcStart,
|
||||||
|
arcX: imageHeight / 2,
|
||||||
|
arcY: imageHeight / 2,
|
||||||
|
canvasHeight: imageHeight,
|
||||||
|
canvasWidth: imageHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算难度等级文字绘制参数。
|
||||||
|
*
|
||||||
|
* @param imageHeight - 难度徽章高度。
|
||||||
|
* @param playLevel - 谱面等级。
|
||||||
|
*/
|
||||||
|
export function createDifficultyLevelTextSpec(
|
||||||
|
imageHeight: number,
|
||||||
|
playLevel: number,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
maxWidth:
|
||||||
|
imageHeight * BANGDREAM_DIFFICULTY_LIST_SPEC.badge.textMaxWidthRatio,
|
||||||
|
text: playLevel.toString(),
|
||||||
|
textSize:
|
||||||
|
imageHeight * BANGDREAM_DIFFICULTY_LIST_SPEC.badge.textSizeRatio,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算难度等级文字居中位置。
|
||||||
|
*
|
||||||
|
* @param imageHeight - 难度徽章高度。
|
||||||
|
* @param levelText - 已渲染的等级文字图片。
|
||||||
|
*/
|
||||||
|
export function getDifficultyLevelTextPosition(
|
||||||
|
imageHeight: number,
|
||||||
|
levelText: ImageLike,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
x: imageHeight / 2 - levelText.width / 2,
|
||||||
|
y: imageHeight / 2 - levelText.height / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -2,6 +2,15 @@ import { Canvas } from 'skia-canvas';
|
|||||||
import { Song } from '@/qqbot/plugins/bangDream/tsugu/models/song';
|
import { Song } from '@/qqbot/plugins/bangDream/tsugu/models/song';
|
||||||
import { drawText } from '@/qqbot/plugins/bangDream/tsugu/canvas/text';
|
import { drawText } from '@/qqbot/plugins/bangDream/tsugu/canvas/text';
|
||||||
import { difficultyColorList } from '@/qqbot/plugins/bangDream/tsugu/models/song';
|
import { difficultyColorList } from '@/qqbot/plugins/bangDream/tsugu/models/song';
|
||||||
|
import {
|
||||||
|
BANGDREAM_DIFFICULTY_LIST_SPEC,
|
||||||
|
createDifficultyBadgeLayout,
|
||||||
|
createDifficultyLevelTextSpec,
|
||||||
|
getDifficultyBadgeColor,
|
||||||
|
getDifficultyLevelTextPosition,
|
||||||
|
getDifficultyListCanvasWidth,
|
||||||
|
getDifficultyListItemX,
|
||||||
|
} from './list-difficulty-spec';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在图片布局层中绘制难度列表。
|
* 在图片布局层中绘制难度列表。
|
||||||
@ -13,12 +22,13 @@ import { difficultyColorList } from '@/qqbot/plugins/bangDream/tsugu/models/song
|
|||||||
*/
|
*/
|
||||||
export function drawDifficultyList(
|
export function drawDifficultyList(
|
||||||
song: Song,
|
song: Song,
|
||||||
imageHeight: number = 60,
|
imageHeight: number = BANGDREAM_DIFFICULTY_LIST_SPEC.list
|
||||||
spacing: number = 10,
|
.defaultImageHeight,
|
||||||
|
spacing: number = BANGDREAM_DIFFICULTY_LIST_SPEC.list.defaultSpacing,
|
||||||
): Canvas {
|
): Canvas {
|
||||||
const difficultyCount = Object.keys(song.difficulty).length;
|
const difficultyCount = Object.keys(song.difficulty).length;
|
||||||
const canvas = new Canvas(
|
const canvas = new Canvas(
|
||||||
imageHeight * difficultyCount + (difficultyCount - 1) * spacing,
|
getDifficultyListCanvasWidth(difficultyCount, imageHeight, spacing),
|
||||||
imageHeight,
|
imageHeight,
|
||||||
);
|
);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
@ -26,7 +36,7 @@ export function drawDifficultyList(
|
|||||||
const i = parseInt(d);
|
const i = parseInt(d);
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
drawDifficulty(i, song.difficulty[i].playLevel, imageHeight),
|
drawDifficulty(i, song.difficulty[i].playLevel, imageHeight),
|
||||||
i * (imageHeight + spacing),
|
getDifficultyListItemX(i, imageHeight, spacing),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -47,22 +57,20 @@ export function drawDifficulty(
|
|||||||
) {
|
) {
|
||||||
const tempCanvas = new Canvas(imageHeight, imageHeight);
|
const tempCanvas = new Canvas(imageHeight, imageHeight);
|
||||||
const ctx = tempCanvas.getContext('2d');
|
const ctx = tempCanvas.getContext('2d');
|
||||||
if (difficultyColorList[difficultyType] != undefined) {
|
const badgeLayout = createDifficultyBadgeLayout(imageHeight);
|
||||||
ctx.fillStyle = difficultyColorList[difficultyType];
|
ctx.fillStyle = getDifficultyBadgeColor(difficultyType, difficultyColorList);
|
||||||
} else {
|
ctx.arc(
|
||||||
ctx.fillStyle = '#f1f1f1';
|
badgeLayout.arcX,
|
||||||
}
|
badgeLayout.arcY,
|
||||||
ctx.arc(imageHeight / 2, imageHeight / 2, imageHeight / 2, 0, 2 * Math.PI);
|
badgeLayout.arcRadius,
|
||||||
ctx.fill();
|
badgeLayout.arcStart,
|
||||||
const levelText = drawText({
|
badgeLayout.arcEnd,
|
||||||
textSize: (imageHeight / 3) * 2,
|
|
||||||
text: playLevel.toString(),
|
|
||||||
maxWidth: imageHeight * 3,
|
|
||||||
});
|
|
||||||
ctx.drawImage(
|
|
||||||
levelText,
|
|
||||||
imageHeight / 2 - levelText.width / 2,
|
|
||||||
imageHeight / 2 - levelText.height / 2,
|
|
||||||
);
|
);
|
||||||
|
ctx.fill();
|
||||||
|
const levelText = drawText(
|
||||||
|
createDifficultyLevelTextSpec(imageHeight, playLevel),
|
||||||
|
);
|
||||||
|
const position = getDifficultyLevelTextPosition(imageHeight, levelText);
|
||||||
|
ctx.drawImage(levelText, position.x, position.y);
|
||||||
return tempCanvas;
|
return tempCanvas;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
import {
|
||||||
|
BANGDREAM_DIFFICULTY_LIST_SPEC,
|
||||||
|
createDifficultyBadgeLayout,
|
||||||
|
createDifficultyLevelTextSpec,
|
||||||
|
getDifficultyBadgeColor,
|
||||||
|
getDifficultyLevelTextPosition,
|
||||||
|
getDifficultyListCanvasWidth,
|
||||||
|
getDifficultyListItemX,
|
||||||
|
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-difficulty-spec';
|
||||||
|
|
||||||
|
describe('BangDream difficulty list spec', () => {
|
||||||
|
it('keeps default difficulty list dimensions stable', () => {
|
||||||
|
expect(BANGDREAM_DIFFICULTY_LIST_SPEC.list.defaultImageHeight).toBe(60);
|
||||||
|
expect(BANGDREAM_DIFFICULTY_LIST_SPEC.list.defaultSpacing).toBe(10);
|
||||||
|
expect(getDifficultyListCanvasWidth(5, 60, 10)).toBe(340);
|
||||||
|
expect(getDifficultyListItemX(3, 60, 10)).toBe(210);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps difficulty badge color fallback stable', () => {
|
||||||
|
expect(getDifficultyBadgeColor(1, ['#111111', '#222222'])).toBe('#222222');
|
||||||
|
expect(getDifficultyBadgeColor(6, ['#111111', '#222222'])).toBe(
|
||||||
|
'#f1f1f1',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps difficulty badge circle layout stable', () => {
|
||||||
|
expect(createDifficultyBadgeLayout(45)).toEqual({
|
||||||
|
arcEnd: Math.PI * 2,
|
||||||
|
arcRadius: 22.5,
|
||||||
|
arcStart: 0,
|
||||||
|
arcX: 22.5,
|
||||||
|
arcY: 22.5,
|
||||||
|
canvasHeight: 45,
|
||||||
|
canvasWidth: 45,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps difficulty level text layout stable', () => {
|
||||||
|
expect(createDifficultyLevelTextSpec(45, 26)).toEqual({
|
||||||
|
maxWidth: 135,
|
||||||
|
text: '26',
|
||||||
|
textSize: 30,
|
||||||
|
});
|
||||||
|
expect(getDifficultyLevelTextPosition(45, { height: 12, width: 20 })).toEqual(
|
||||||
|
{
|
||||||
|
x: 12.5,
|
||||||
|
y: 16.5,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user