Limbus Company의 덱 빌더는 12명의 수감자에게 각각 인격과 EGO를 배정하는 구조다. 메타 편성은 이미 커뮤니티에서 충분히 공유되고 있지만, "랜덤으로 굴려서 플레이한다"는 수요가 생각보다 많았다. 랜덤 챌린지, 축제 컨텐츠, 또는 단순히 매너리즘을 깨고 싶은 유저들을 위한 기능이다.
단순히 Math.random()으로 12개를 뽑으면 끝나는 것 같지만, 실제로 만들어보면 고려할 게 많다.
OR 로직이 더 관대하지만, AND가 더 재미있다. "출혈+진동을 동시에 쓰는 인격"을 조건으로 거는 게 실제 게임 플레이에서도 의미가 있기 때문이다.
조건 검증
랜덤을 돌리기 전에 조건이 만족 가능한지 먼저 검증한다. 보유 현황 연동 시 풀이 좁아지면 "출혈 5명" 조건을 채울 수 없을 수 있다.
typescript
export function validateConditions(
sinners: Sinner[],
identitiesBySinner: Record<string, Identity[]>,
conditions: KeywordCondition[],
): string | null {
// 각 수감자에게 조건을 만족하는 인격이 있는지 확인
let count = 0
for (const sinner of sinners) {
const ids = identitiesBySinner[sinner.id] ?? []
if (ids.some(matchesAll)) count++
}
if (count < maxMinCount) {
return `보유 인격으로는 ${kwLabel} ${maxMinCount}명 조건을 만족할 수 없습니다`
}
return null
}
불가능한 조건이면 Toast로 사유를 알려주고 롤을 막는다. 이게 없으면 무한 루프에 빠지거나 의미 없는 결과가 나온다.
키워드 우선 배정
키워드 조건이 있으면 조건을 만족하는 수감자부터 먼저 배정하고, 나머지를 채운다:
typescript
function keywordAwareAssign(sinners, idsBySinner, conditions) {
// 1. 조건 만족 가능한 수감자들을 셔플
const canProvide = sinners.filter(s =>
idsBySinner[s.id].some(matchesAll)
)
const shuffled = shuffle(canProvide)
// 2. 필요한 수만큼 키워드 인격 배정
for (let i = 0; i < maxMinCount; i++) {
const pool = idsBySinner[shuffled[i].id].filter(matchesAll)
result[shuffled[i].id] = pool[Math.floor(Math.random() * pool.length)]
}
// 3. 나머지 수감자는 자유롭게
for (const s of sinners) {
if (!usedSinners.has(s.id)) {
result[s.id] = randomPick(idsBySinner[s.id])
}
}
}
Fisher-Yates 셔플로 매번 다른 수감자가 키워드 슬롯에 들어간다.
EGO 등급별 확률
EGO는 인격과 다르게 등급별로 하나씩 뽑되, 등급마다 등장 확률을 다르게 했다:
ZAYIN: 항상 1개
TETH, HE, WAW, ALEPH: 각각 50% 확률
typescript
// ZAYIN: always pick one
if (byGrade['ZAYIN']?.length) {
picked.push(randomPick(byGrade['ZAYIN']))
}
// Other grades: 50% chance
for (const grade of ['TETH', 'HE', 'WAW', 'ALEPH']) {
if (byGrade[grade]?.length && Math.random() < 0.5) {
picked.push(randomPick(byGrade[grade]))
}
}
모든 등급을 무조건 뽑으면 너무 많고, 아예 안 뽑으면 허전하다. ZAYIN은 기본 장비 느낌으로 항상 포함하고, 나머지는 확률로 굴려서 매번 다른 조합이 나오게 했다.
카드 UI: 고스트 카드에서 시네마틱 리빌까지
고스트 카드
결과가 없을 때 빈 슬롯을 보여주는데, 단순히 빈 박스를 두면 밋밋하다. 수감자 로고를 워터마크로 깔고, 코너 장식과 이름 힌트를 넣어 "아직 뽑히지 않은 카드" 느낌을 줬다.