숫자 4개를 입력하여 숫자를 맞추는 게임이다. 추가적으로 난수 생성, input 유효성검사와 횟수 제한, 숫자 리스트, 모달창 구현을 하였다. 목업 툴을 통해 대략적으로 구상해본 화면이다.
유저가 맞출 숫자를 4개의 난수로 생성해놔야한다.
1️⃣ candidates
: 1부터 9까지의 숫자로 구성.
2️⃣ chosen
: 랜덤으로 숫자 생성.
4️⃣ array.push(chosen)
: 숫자를 array
배열에 저장.
const getNumbers = () => {
const candidates = [1, 2, 3, 4, 5, 6, 7, 8, 9]; // 1️⃣ 번
const array = [];
for (let i = 0; i < 4; i += 1) {
// 랜덤 숫자 4개 중복 없이
const chosen = candidates.splice(Math.floor(Math.random() * (9 - i)), 1)[0];//2️⃣ 번
array.push(chosen); // 3️⃣ 번
}
return array;
};
...
const [answer, setAnswer] = useState(getNumbers());
1️⃣ onlyNumber
: 입력된 숫자가 0에서 9 사이의 숫자를 만족해야 한다는 정규식을 가진 replace된 문자열
2️⃣ setWarning
: onlyKorean의 조건을 만족 못할 경우 경고 문자 출력.
3️⃣ disabled
: 빈값 제출을 막기 위해 value값이 존재할 경우 제출되게 설정.
const [warning, setWarning] = useState('');
...
const onChangeValue = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const onlyNumber = e.target.value.replace(/[^0-9]/g, '');
if (onlyNumber.length !== 4) {
setWarning('4자를 입력하세요');
}
if (!onlyNumber) {
setWarning('숫자를 입력해주세요');
}
setValue(onlyNumber);
},
[]
);
...
<button type="submit" disabled={!value}></button>
✅ 정답일 경우
1️⃣ 입력한 숫자와 answer가 같은 경우
answer.join('')
: 난수 4개로 이루어진 배열을 join을 통해 문자열화.2️⃣ setTries
: tries에 try와 result값을 가진 객체를 삽입
const [answer, setAnswer] = useState(getNumbers());
...
const onSubmitForm = useCallback<(e: React.FormEvent) => void>(
e => {
...
if (value === answer.join('')) { 1️⃣ // 번
setTries(t => [ // 2️⃣ 번
...t,
{
try: value,
result: '홈런!',
},
]);
setModalOpen(true); // 모달창 오픈
setValue(''); // 입력값 초기화
}
...
},
[value, answer]
);
✅ 오답일 경우
1️⃣ answerArray
: 입력한 4개의 숫자를 각각 나눈다.
2️⃣ 만약 기회를 10번 모두 사용시 실행 항목.
3️⃣ answer
와 answerArray
의 첫 숫자부터 끝숫자까지 하나씩 비교
strike
ball
const onSubmitForm = useCallback<(e: React.FormEvent) => void>(
e => {
...
else {
const answerArray = value.split('').map(v => parseInt(v)); // 1️⃣ 번
let strike = 0;
let ball = 0;
if (tries.length >= 9) { // 2️⃣ 번
setResult(`답은 ${answer.join('')}`); // state set은 비동기
setModalOpen(true);
setValue('');
setAnswer(getNumbers());
setTries([]);
input && input.focus();
}
else {
console.log('답은', answer.join(''));
for (let i = 0; i < 4; i += 1) {
if (answerArray[i] === answer[i]) { // 3️⃣ 번
console.log('strike', answerArray[i], answer[i]);
strike += 1;
} else if (answer.includes(answerArray[i])) {
console.log(
'ball',
answerArray[i],
answer.indexOf(answerArray[i])
);
ball += 1;
}
}
setTries(t => [...t,
{
try: value,
result: `${strike} Strike, ${ball} Ball`,
},
]);
setValue('');
input && input.focus();
}
}
},
[value, answer]
);
내가 입력한 숫자에 대한 strike
, ball
판별 리스트를 출력해보자.
유저가 시도한 숫자와 그에 대한 결과값을 가진 tries배열
을 map
을 통해 출력한다.
컴포넌트 간의 props
전달에 대한 type
정의를 알아보기 위하여 별도의 Try컴포넌트
를 생성했다.
✅ NumberBaseball.tsx
// 빈배열의 경우 항상 타이핑 문제가 일어난다.
const [tries, setTries] = useState<TryInfo[]>([]);
...
<ul className="flex flex-wrap text-center font-semibold text-[#124753]">
{tries.map((v, i) => (
<Try key={`${i + 1}차 시도 : ${v.try}`} tryInfo={v} />
))}
</ul>
✅ Try.tsx
const Try = ({ tryInfo }: { tryInfo: TryInfo }) => {
return (
<li className="mx-auto my-3 rounded-2xl border-2 border-[#5061b3] px-3 py-2">
<div className="font-black">{tryInfo.try}</div> // 시도한 숫자
<div>{tryInfo.result}</div> // strike, ball 판별 결과. (또는 홈런)
</li>
);
};
tries
의 길이가 곧 시도 횟수이다.result
을 전달한다.const [result, setResult] = useState('');
...
if (value === answer.join('')) {
setResult(`총 ${tries.length}번만에 성공`);
...
} else {
setResult(`답은 ${answer.join('')}`);
...
}
...
{modalOpen && <Modal baseballResult={result} baseball={'숫자야구'} />}
💡 모달창
: 모달창 하나를 재사용하기 위하여 게임마다 보여줘야할 결과를 props로 Modal컴포넌트에 전달한다.
자세히 👉 모달창 기능 구현