PJH's Game World - 숫자야구

박정호·2022년 12월 9일
0

Game Project

목록 보기
5/13
post-thumbnail

🚀 Start

숫자 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());


✔️ input 유효성검사

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️⃣ answeranswerArray의 첫 숫자부터 끝숫자까지 하나씩 비교

  • 숫자가 동일하다면 strike
  • 숫자가 동일하지는 않지만, answer배열 안에 존재하는 숫자라면 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컴포넌트에 전달한다.
자세히 👉 모달창 기능 구현



🖥 실제 구현 화면

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글