로또 숫자 맞추기 게임이다. 유저가 로또 숫자 선택, 랜덤 로또 숫자 6개 출력, 일치/불일치 판별, 입력 input 유효성 검사, 모달창 구현을 하였다. 목업 툴을 통해 대략적으로 구상해본 화면이다.
1️⃣ form
: 1 ~ 45 중의 숫자를 입력.
2️⃣ myLottoList.length < 6
: 로또 숫자는 6개까지 입력 가능.
!myLottoList.includes(parseInt(value))
: 아직 입력하지 않은 숫자라면...setMyLottoList
: myLottoList 배열에 입력 숙자 저장.3️⃣ myLottoList 배열 안의 입력된 숫자들을 오름차순 정렬하여 하나씩 출력.
const [myLottoList, setMyLottoList] = useState<number[]>([]);
...
const onSubmitForm = useCallback<(e: React.FormEvent) => void>(
e => {
e.preventDefault();
if (myLottoList.length < 6) { // 2️⃣ 번
if (!myLottoList.includes(parseInt(value))) {
setMyLottoList(myLottoList.concat(parseInt(value)));
} else {
setWarning('숫자가 중복됩니다');
}
} else {
setWarning('로또번호는 6개까지입니다');
}
setValue('');
},
[value]
);
<form onSubmit={onSubmitForm}> // 1️⃣ 번
<input ... min="1" max="45"/>
<button type="submit" disabled={!value}></button>
</form>
...
{myLottoList // 3️⃣ 번
.sort((a, b) => a - b)
.map((lotto, i) => {
return (
<div className="mx-auto">
<Ball key={`${i + 1}차 시도 : ${lotto}`} number={lotto} />
</div>
);
})}
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 === '0' || onlyNumber > '45') {
setWarning('1부터 45 범위 안에서 입력하세요');
}
if (!onlyNumber) {
setWarning('숫자를 입력해주세요');
}
setValue(onlyNumber);
},
[]
);
...
<button type="submit" disabled={!value}></button>
유저가 맞출 로또 숫자 6개 랜덤 생성.
1️⃣ 유저가 숫자 6개를 모두 입력하였다면, 당첨확인 버튼 클릭.
2️⃣ startLotto
: 1초에 하나씩 랜덤 숫자 출력.
const lottoNumbers = useMemo(() => getWinNumbers(), []);
const [winNumbers] = useState(lottoNumbers);
...
const startLotto = () => { // 2️⃣ 번
for (let i = 0; i < winNumbers.length - 1; i++) {
timeouts.current[i] = window.setTimeout(() => {
setWinBalls(prevBalls => [...prevBalls, winNumbers[i]]);
...
}, (i + 1) * 1000);
}
return () => {
timeouts.current.forEach(v => {
clearTimeout(v);
});
};
};
...
{myLottoList.length === 6 && ( // 1️⃣ 번
<button onClick={() => {startLotto(); ...}}
{buttonText ? '당첨숫자' : '당첨 확인'}
</button>
)}
✅ 랜덤생성기
const getWinNumbers = () => { const candidate = Array(45) .fill(null) .map((v, i) => i + 1); const shuffle = []; while (candidate.length > 0) { shuffle.push( candidate.splice(Math.floor(Math.random() * candidate.length), 1)[0] ); } const winNumbers = shuffle.slice(0, 6).sort((p, c) => p - c); return [...winNumbers]; }
만약 내가 입력한 숫자들이 담긴 myLottoList
에 랜덤으로 생성된 숫자(winNumbers[i]
)가 포함되어있다면 result
를 1씩 증가.
const startLotto = () => {
...
myLottoList.includes(winNumbers[i]) &&
setResult(prevResult => (prevResult += 1));
}
const Ball: FC<{ number: number }> = ({ number }) => {
let background;
if (number <= 10) {
background = '#ff6380';
} else if (number <= 20) {
background = '#fca272';
} else if (number <= 30) {
background = '#fff894';
} else if (number <= 40) {
background = '#1773ac';
} else {
background = '#acc383';
}
return (
<div style={{ background }}> {number} ![](https://velog.velcdn.com/images/pjh1011409/post/03041fb5-0674-4623-bf8a-e6c54a37fc77/image.gif)
</div>
);
};
export default Ball;