This commit is contained in:
2026-04-24 15:42:54 +09:00
parent be4c383b6f
commit cd38fd49e2
18 changed files with 11609 additions and 235 deletions

View File

@@ -1,86 +1,55 @@
import { Bar, Course } from 'tja-parser';
export interface Factors {
physical_density: number;
stamina_requirement: number;
pattern_complexity: number;
rhythmic_complexity: number;
reading_gimmick: number;
}
import { Course, Bar } from 'tja-parser';
export namespace Factor {
export function getAllNotes(course: Course): any[] {
const notes: any[] = [];
// 노트 추출 및 정렬 (시간순)
export function getAllNotes(course: Course) {
const notes: { type: number, time: number }[] = [];
for (const group of course.noteGroups) {
if (group instanceof Bar) {
notes.push(...group.getNotes());
// 파서 구조에 따라 노트 추출 방식 조정 필요
notes.push(...group.getNotes().map((note) => {
return {
//@ts-expect-error
type: note.type,
time: note.getTimingMS()
}
}));
}
}
return notes;
return notes.sort((a, b) => a.time - b.time).filter(n => [1, 2, 3, 4].includes(n.type));
}
export function getPhysicalDensity(course: Course): number {
const notes = getAllNotes(course);
export function getAverageDensity(notes: { time: number }[]): number {
if (notes.length < 2) return 0;
const duration = (notes[notes.length - 1].time - notes[0].time) / 1000;
return duration > 0 ? notes.length / duration : 0;
}
export function getPeakDensity(notes: { time: number }[]): number {
if (notes.length === 0) return 0;
const bars = course.noteGroups.length;
return notes.length / (bars || 1);
}
export function getStaminaRequirement(course: Course): number {
const notes = getAllNotes(course);
let maxStream = 0;
let currentStream = 0;
for (const note of notes) {
if (note.type !== '0') {
currentStream++;
} else {
maxStream = Math.max(maxStream, currentStream);
currentStream = 0;
let maxPeak = 0;
const windowSize = 1000;
for (let i = 0; i < notes.length; i++) {
let count = 0;
const startTime = notes[i].time;
for (let j = i; j < notes.length && notes[j].time < startTime + windowSize; j++) {
count++;
}
maxPeak = Math.max(maxPeak, count);
}
return Math.max(maxStream, currentStream) / 100;
return maxPeak;
}
export function getPatternComplexity(course: Course): number {
const notes = getAllNotes(course).filter(n => n.type === '1' || n.type === '2');
let transitions = 0;
for (let i = 1; i < notes.length; i++) {
if (notes[i].type !== notes[i-1].type) {
transitions++;
}
}
return notes.length > 0 ? transitions / notes.length : 0;
}
export function getRhythmicComplexity(course: Course): number {
let complexNotes = 0;
const allNotes = getAllNotes(course);
for (const group of course.noteGroups) {
if (group instanceof Bar) {
const division = group.getNotes().length;
if (division % 4 !== 0 || division > 16) {
complexNotes += division;
}
}
}
return allNotes.length > 0 ? complexNotes / allNotes.length : 0;
}
export function getReadingGimmick(course: Course): number {
// BPM 변화나 Scroll 변화 빈도 측정
// tja-parser의 구조에 따라 구현 (여기서는 placeholder)
return 0;
export function getMaxCombo(notes: any[]): number {
return notes.length;
}
}
export function factorize(course: Course): Factors {
export function factorize(course: Course) {
const notes = Factor.getAllNotes(course);
return {
physical_density: Factor.getPhysicalDensity(course),
stamina_requirement: Factor.getStaminaRequirement(course),
pattern_complexity: Factor.getPatternComplexity(course),
rhythmic_complexity: Factor.getRhythmicComplexity(course),
reading_gimmick: Factor.getReadingGimmick(course)
average_density: Factor.getAverageDensity(notes),
peak_density: Factor.getPeakDensity(notes),
max_combo: Factor.getMaxCombo(notes)
};
}
}