앱인토스 미니앱 10 – 결정의 신
앱인토스 미니앱 제작기 열 번째는 결정의 신입니다. 점심 메뉴나 미뤄 둔 연락처럼 사소하지만 은근히 시간을 잡아먹는 선택을 대신 정해 주는 앱이에요.
동전 던지기나 사다리 타기 같은 도구를 모아 둔 앱인데, 첫 화면에는 도구 대신 카드 한 장을 뒀습니다. 이 글은 그 카드를 어떻게 만들었는지에 대한 기록입니다.
결정의 신은 어떤 앱인토스 미니앱인가



홈 맨 위에 “오늘의 한 수” 카드가 있고, 그 아래로 결정 도구 다섯 개가 이어집니다. 운명의 동전, 선택의 저울, 사다리 타기, 신의 계시, 제비뽑기예요. 도구로 내린 결정은 기록 화면에서 다시 볼 수 있고, 매주 도구 하나가 포인트를 두 배로 주는 특별 모드로 올라옵니다.
고민이 없는 날에도 열 이유
결정 도구는 고민이 있을 때만 찾게 됩니다. 정할 게 없는 날에는 열 일이 없죠. 그래서 앱이 먼저 질문을 꺼내게 했습니다.
결정 도구
- 내가 고민을 들고 와야 쓴다
- 도구를 고르고 직접 돌린다
오늘의 한 수
- 앱이 오늘의 질문을 꺼낸다
- 카드를 누르면 답과 이유가 나온다
카드에는 “오후 커피 마실까 말까” 같은 질문이 적혀 있고, 누르면 뒤집히면서 “마신다”와 한 줄 이유가 나옵니다. 운세처럼 두루뭉술한 말이 아니라 오늘 실제로 할 수 있는 결정 하나를 주는 게 기준이었어요. 질문은 전부 점심, 연락, 정리처럼 일상에서 흔히 고민하는 것들로 채웠습니다.
카드를 뒤집은 날은 연속 기록이 올라가고, 뒤집은 카드 아래에는 “내일 새 한 수가 열려요”라고 적혀 있습니다.
앱인토스 미니앱 3 – 로또 번호 생성기같은 날에는 같은 한 수
오늘의 한 수는 하루에 하나입니다. 열 때마다 바뀌지 않도록 결과를 날짜에 묶었어요.
/** KST 날짜 문자열(yyyy-MM-dd) 하나로 오늘의 한 수를 정한다. */
export function getDailyMove(date: string = getKSTDateString()): DailyMove {
const parsed = diffInDays(EPOCH, date);
const dayIndex = Number.isFinite(parsed) ? parsed : 0;
const n = DAILY_DECK.length;
const cycle = Math.floor(dayIndex / n);
const pos = ((dayIndex % n) + n) % n;
const dilemma = DAILY_DECK[cycleOrder(n, cycle)[pos]];
// FNV-1a의 최하위 비트는 날짜 끝자리 짝홀을 따라가 번갈아 나온다. 난수열을 한 번 거쳐 쓴다.
const pickIndex = (seededRandom(fnv1a(`project3-pick-${date}`))() < 0.5 ? 0 : 1) as 0 | 1;
return {
date,
dilemma,
pickIndex,
pick: dilemma.options[pickIndex],
other: dilemma.options[1 - pickIndex],
reason: dilemma.reasons[pickIndex],
};
}기준일부터 오늘까지 며칠이 지났는지를 세고, 그 숫자로 질문을 고릅니다. 두 선택지 중 어느 쪽인지는 날짜 문자열을 숫자로 바꾼 값을 씨앗으로 삼아 정해요. 저장해 둔 값을 꺼내는 게 아니라 날짜에서 매번 다시 계산하는 방식이라, 같은 날이면 몇 번을 열어도 어느 기기에서 열어도 결과가 같습니다.
날짜 값을 그대로 쓰지 않는다
날짜를 숫자로 바꾼 값의 마지막 비트를 그대로 쓰면, 날짜 끝자리가 짝수냐 홀수냐에 따라 답이 거의 하루씩 번갈아 나옵니다. 그래서 그 값을 난수 생성기의 씨앗으로 넣고 거기서 나온 첫 숫자로 고릅니다. 코드 중간의 주석이 그 얘기예요.
질문이 겹치지 않게 섞는다
질문을 매일 무작위로 뽑으면 어제 나온 질문이 오늘 또 나올 수 있습니다. 이 앱은 카드 덱처럼 다룹니다. 전체 질문을 한 번 섞어서 차례대로 쓰고, 다 쓰면 새로 섞어요.
function shuffledOrder(length: number, cycle: number): number[] {
const order = Array.from({ length }, (_, i) => i);
const rand = seededRandom(fnv1a(`project3-cycle-${cycle}`));
for (let i = length - 1; i > 0; i -= 1) {
const j = Math.floor(rand() * (i + 1));
[order[i], order[j]] = [order[j], order[i]];
}
return order;
}
/** 주기 경계에서 앞 주기의 마지막 질문이 이틀 연속 나오지 않도록 첫 두 칸을 바꾼다. */
function cycleOrder(length: number, cycle: number): number[] {
const order = shuffledOrder(length, cycle);
if (length > 1) {
const prev = shuffledOrder(length, cycle - 1);
if (order[0] === prev[length - 1]) [order[0], order[1]] = [order[1], order[0]];
}
return order;
}섞는 순서도 몇 번째 바퀴인지를 씨앗으로 정하기 때문에 누가 열어도 같은 순서가 나옵니다. 한 바퀴 안에서는 같은 질문이 두 번 나오지 않아요.
남는 구멍이 하나 있습니다. 앞 바퀴의 마지막 질문과 새 바퀴의 첫 질문이 우연히 같으면 이틀 연속 같은 질문을 보게 되죠. cycleOrder가 그 경우를 확인해서, 겹치면 새 바퀴의 첫 두 칸을 맞바꿉니다.
도구는 고민의 모양에 맞춰 고른다
카드 아래의 도구 다섯 개는 쓰임이 조금씩 다릅니다.
도구 목록 맨 위에는 “이번 주의 특별 모드”가 하나 올라옵니다.
알림도 있습니다. 매주 금요일 저녁 6시에 이번 주 선택 기록을 돌아보라고 알려 주는 알림인데, 앱을 열자마자 묻지 않고 오늘의 한 수를 처음 뒤집은 뒤에야 신청 카드가 보여요.
오락이라는 걸 분명히
이 앱이 정해 주는 건 가벼운 선택입니다. 의료나 법률, 금융처럼 무게가 있는 판단에 쓰는 앱이 아니라는 점을 앱 소개에 적어 뒀어요. 오늘의 한 수 질문도 어느 쪽이 나와도 곤란하지 않은 것들입니다.
오늘의 한 수는 광고 없이 열립니다. 광고를 보는 쪽을 고르면 행운 시간과 오늘 피할 것 한 가지가 덤으로 붙는데, 이것도 날짜로 고정돼 있어서 하루 동안 같은 내용이 나와요.
다음에 할 일
오늘의 한 수 질문을 계속 다듬어 가려고 합니다. 덱이 클수록 같은 질문을 다시 만나기까지 시간이 길어지니, 질문을 늘리는 것도 그중 하나예요.
프로젝트를 어떤 식으로 진행하는지는 프로젝트 진행 방식에, 날짜에서 값을 계산해 쓰는 또 다른 앱 이야기는 로또 번호 생성기 글에 있습니다.
결정의 신 자주 묻는 질문
결정의 신은 어떤 앱인가요?
매일 바뀌는 오늘의 한 수 카드를 뒤집어 오늘 내릴 결정 하나를 받고, 동전 던지기, 선택의 저울, 사다리 타기, 신의 계시, 제비뽑기로 다른 고민도 정하는 앱입니다. 일상의 가벼운 선택을 위한 오락형 콘텐츠입니다.
오늘의 한 수는 열 때마다 바뀌나요?
바뀌지 않습니다. 날짜 하나로 결과가 정해지기 때문에 같은 날에는 몇 번을 열어도 같은 한 수가 나오고, 다음 날 새 한 수가 열립니다.
중요한 결정에 써도 되나요?
점심 메뉴나 미뤄 둔 연락 같은 가벼운 선택을 위한 앱입니다. 의료, 법률, 금융처럼 중요한 판단에 쓰는 용도가 아닙니다.
이 글은 앱인토스 미니앱 「결정의 신」의 기록입니다. 앱인토스는 토스 앱 안에서 미니앱을 서비스하는 플랫폼이고, 공식 안내는 앱인토스 개발자센터에 있습니다.
이 글은 직접 만들고 운영하며 남긴 기록입니다. 적힌 수치는 작성 시점의 제 계정 기준이며, 같은 결과나 수익을 보장하지 않습니다.

One Comment