124 lines
3.3 KiB
TypeScript
124 lines
3.3 KiB
TypeScript
import {
|
|
getPenControlPattern,
|
|
PEN_CONTROL_PATTERN_IDS,
|
|
} from '../data/penControlPatterns';
|
|
import type { PenControlCellStyle } from '../shared/penControlDrawUtils';
|
|
|
|
export const PEN_CONTROL_MODE = 'pen-control-mix' as const;
|
|
export type PenControlMode = typeof PEN_CONTROL_MODE;
|
|
|
|
export const ROWS_PER_PATTERN = 2;
|
|
export const MIN_PATTERNS = 0;
|
|
export const MAX_PATTERNS = 5;
|
|
export const DEFAULT_PATTERN_COUNT = 5;
|
|
|
|
export interface PenControlRowSpec {
|
|
role: 'pair-first' | 'pair-second';
|
|
cells: PenControlCellStyle[];
|
|
}
|
|
|
|
export interface PenControlPatternBlock {
|
|
patternId: string;
|
|
patternName: string;
|
|
rows: [PenControlRowSpec, PenControlRowSpec];
|
|
}
|
|
|
|
export interface PenControlSheetLayout {
|
|
maxRow: number;
|
|
maxCol: number;
|
|
cellSize: number;
|
|
startX: number;
|
|
startY: number;
|
|
colGap: number;
|
|
rowGap: number;
|
|
}
|
|
|
|
export interface PenControlSheetData {
|
|
mode: PenControlMode;
|
|
patternCount: number;
|
|
blocks: PenControlPatternBlock[];
|
|
layout: PenControlSheetLayout;
|
|
}
|
|
|
|
export function clampPatternCount(
|
|
requested: number,
|
|
maxRow: number,
|
|
): number {
|
|
const capByRows = Math.floor(maxRow / ROWS_PER_PATTERN);
|
|
const capped = Math.min(
|
|
MAX_PATTERNS,
|
|
Math.max(MIN_PATTERNS, requested),
|
|
Math.max(MIN_PATTERNS, capByRows),
|
|
);
|
|
return capped;
|
|
}
|
|
|
|
function buildRowCells(
|
|
maxCol: number,
|
|
_role: 'pair-first' | 'pair-second',
|
|
): PenControlCellStyle[] {
|
|
const cells: PenControlCellStyle[] = [];
|
|
for (let c = 0; c < maxCol; c++) {
|
|
cells.push(c === 0 ? 'reference' : 'guide');
|
|
}
|
|
return cells;
|
|
}
|
|
|
|
function normalizePatternIds(
|
|
patternIds: string[],
|
|
patternCount: number,
|
|
): string[] {
|
|
const valid = patternIds.filter((id) =>
|
|
PEN_CONTROL_PATTERN_IDS.includes(id),
|
|
);
|
|
const unique: string[] = [];
|
|
for (const id of valid) {
|
|
if (!unique.includes(id)) unique.push(id);
|
|
}
|
|
|
|
if (unique.length >= patternCount) {
|
|
return unique.slice(0, patternCount);
|
|
}
|
|
|
|
const rest = PEN_CONTROL_PATTERN_IDS.filter((id) => !unique.includes(id));
|
|
const shuffled = [...rest].sort(() => Math.random() - 0.5);
|
|
while (unique.length < patternCount && shuffled.length > 0) {
|
|
unique.push(shuffled.shift()!);
|
|
}
|
|
return unique.slice(0, patternCount);
|
|
}
|
|
|
|
export function generatePenControlMix(options: {
|
|
patternIds: string[];
|
|
patternCount: number;
|
|
layout: PenControlSheetLayout;
|
|
}): PenControlSheetData {
|
|
const { layout } = options;
|
|
const patternCount = clampPatternCount(
|
|
options.patternCount,
|
|
layout.maxRow,
|
|
);
|
|
const ids = normalizePatternIds(options.patternIds, patternCount);
|
|
|
|
const blocks: PenControlPatternBlock[] = ids.map((patternId) => {
|
|
const pattern = getPenControlPattern(patternId);
|
|
const pairFirst = buildRowCells(layout.maxCol, 'pair-first');
|
|
const pairSecond = buildRowCells(layout.maxCol, 'pair-second');
|
|
return {
|
|
patternId,
|
|
patternName: pattern?.name ?? patternId,
|
|
rows: [
|
|
{ role: 'pair-first', cells: pairFirst },
|
|
{ role: 'pair-second', cells: pairSecond },
|
|
],
|
|
};
|
|
});
|
|
|
|
return {
|
|
mode: PEN_CONTROL_MODE,
|
|
patternCount: blocks.length,
|
|
blocks,
|
|
layout,
|
|
};
|
|
}
|