✍🏻 개발 시작 전, 약식 기획서 작성해두면 훨씬 수월 !
- 프로젝트 개요
- 이 프로젝트는 어떤 용도로 만드는지 간단히 설명
- (예: "가위바위보 게임 웹앱")
- 전체 와이프레임
- 화면 흐름이 어떻게 되는지 대략적으로 그림으로 정리
- 확정된 디자인이 있으면 첨부
- 없다면 색상, 폰트, 버튼 스타일 정도라도 간단히
- 비즈니스 로직 설명
- 웹/앱이 어떤 규칙으로 동작하는 지 설명
- (예: 가위바위보 게임 → 유저 선택 → 컴퓨터 랜덤 선택 → 승패 판정)
- 주요 기능 리스트
- 프로젝트에 들어갈 핵심 기능 나열
- (예: 사용자 선택 , 컴퓨터 랜덤 선택, 승패 결과 표시, 리셋 기능)
📚 어디에 정리하면 좋을까?
- Figma
→ 와이어프레임, 시안 정리하기 최고 - GitHub README
→ 개발 전에 간단한 기획서 내용을 정리해두면
→ 팀원이랑 공유하거나 기록용으로도 딱 좋다.
📌 참고한 강사님의 가위바위보 게임 기획서
https://somyclass.notion.site/1d0973f5e0fe80fb9343f7e67249e537
가위 바위 보 게임 | Notion
assets.zip
somyclass.notion.site
✨ 프로젝트: 가위바위보 게임
가위바위보 게임은 사용자와 컴퓨터가 각각 가위 / 바위 / 보를 선택해 승패를 겨루는 간단한 React 기반 게임입니다.
https://rps-game-dun.vercel.app/
가위바위보 게임
rps-game-dun.vercel.app
🛠️ 사용 기술
- React(Vite 기반)
- CSS Module로 컴포넌트 스타일링
- Vercel을 통한 배포
📁 프로젝트 구조
/public
└── vite.svg
/src
├── /assets
│ ├── scissors.png # 가위 이미지
│ ├── rock.png # 바위 이미지
│ ├── paper.png # 보 이미지
│ └── questionmark.png # 선택 전 물음표 이미지
│
├── /components
│ ├── Button.jsx # 가위/바위/보 버튼 컴포넌트
│ └── Card.jsx # 유저/컴퓨터 선택 카드 컴포넌트
│
├── /css
│ ├── App.module.css # App 컴포넌트 스타일
│ ├── Button.module.css # Button 컴포넌트 스타일
│ └── Card.module.css # Card 컴포넌트 스타일
│
├── /utils
│ ├── game.js # 승패 결정 및 컴퓨터 선택 유틸 함수
│ └── result.js # 결과 뒤집는 유틸 함수
├── App.jsx # 메인 App 컴포넌트
├── index.css # 전역 스타일
└── main.jsx
indx.html
package.json
README.md
vite.config.js
...
👩🏻💻 개발 사항
1️⃣ Choice 객체 배열
- 가위/바위/보의 이름, 이미지 정보, 타입을 가진 객체 배열 생성
- useMemo 훅을 통해서 최초 한 번만 배열을 만들고, 그 이후 렌더링에서 재사용하도록 함
const choice = useMemo(
() => [
{ name: '가위', imgUrl: scissors, type: 'scissors' },
{ name: '바위', imgUrl: rock, type: 'rock' },
{ name: '보', imgUrl: paper, type: 'paper' },
],
[]
)
2️⃣ 기본 UI 레이아웃 구성 및 스타일 적용
- App.jsx
<div className={styles.app}>
<h1>가위바위보 게임</h1>
<div className={styles.main}>
<Card
title="너님"
choice={userChoice}
type="user"
result={result}
isPlaying={isPlaying}
count={count}
/>
<div className={styles.btn__con}>
{choice.map(c => (
<Button
key={c.type}
name={c.name}
imgUrl={c.imgUrl}
type={c.type}
onClick={() => handleUserChoice(c)}
disabled={isPlaying}
/>
))}
<span className={styles['btn__con--common']}>{result}</span>
{(userChoice || computerChoice) && (
<button
className={`${styles['btn--retry']} ${styles['btn__con--common']}`}
onClick={handleRetry}
disabled={isPlaying}
>
다시하기
</button>
)}
</div>
<Card
title="상대선수"
choice={computerChoice}
type="computer"
result={reverseResult(result)}
isPlaying={isPlaying}
count={count}
/>
</div>
<div className={styles.description}>
버튼을 클릭하여 가위, 바위, 보 중 하나를 선택하세요.
<br />
컴퓨터는 랜덤으로 선택합니다.
</div>
</div>
3️⃣ 상태 관리 구현
상태 | 역할 |
userChoice | 사용자가 고른 가위/바위/보 저장 |
computerChoice | 컴퓨터가 고른 가위/바위/보 저장 |
result | 승/패/비김 결과 저장 |
isPlaying | 게임이 진행 중인지 여부 관리 |
count | 카운트다운 숫자 저장 및 관리 |
const [userChoice, setUserChoice] = useState(null)
const [computerChoice, setComputerChoice] = useState(null)
const [result, setResult] = useState('?')
const [isPlaying, setIsPlaying] = useState(false) // 플레이 상태
const [count, setCount] = useState(null) // 카운트
4️⃣ Card, Button 컴포넌트 분리
- Card 컴포넌트: 유저와 컴퓨터가 선택한 결과를 보여주는 카드 형태의 컴포넌트
- 상태에 따라 유동적으로 화면이 바뀜 (카운트다운 ➡️ 선택 결과 표시)
- 승패에 따라 스타일 변동 (조건부 스타일링)
- 유저와 컴퓨터 카드 모두 재사용 가능
import React from 'react'
import questionMark from '../assets/questionmark.png'
import styles from '../css/Card.module.css'
const Card = ({ title, choice, type, result, isPlaying, count }) => {
const getResultClass = () => {
if (result === '이겼다') return styles.win
if (result === '졌다') return styles.lose
return ''
}
const content = () => {
if (isPlaying && count > 0) {
return (
<>
<div className={styles.count}>{count}</div>
<p>카운트다운</p>
</>
)
}
if (choice) {
return (
<>
<img src={choice.imgUrl} alt={choice.type} />
<p>{result}</p>
</>
)
}
return (
<>
<img src={questionMark} alt="?" />
<p>선택하세요</p>
</>
)
}
return (
<div className={`${styles.card} ${styles[type]} ${getResultClass()}`}>
<h2>{title}</h2>
{content()}
</div>
)
}
export default Card
- Button 컴포넌트: 유저가 가위/바위/보를 선택할 수 있도록 하는 버튼 컴포넌트
- 가위 / 바위 / 보 각각 다른 스타일 적용
- 게임 진행 중이면 버튼 비활성화
import React from 'react'
import styles from '../css/Button.module.css'
const Button = ({ name, imgUrl, type, onClick, disabled }) => {
return (
<button
className={`${styles.btn} ${styles[`btn--${type}`]}`}
onClick={onClick}
disabled={disabled}
>
<img src={imgUrl} alt={name} />
<p>{name}</p>
</button>
)
}
export default Button
5️⃣ 컴퓨터 랜덤 선택 함수 구현
컴퓨터는 랜덤으로 가위, 바위, 보 중 하나를 선택
const generateComputerChoice = choice => {
const randomIdx = Math.floor(Math.random() * choice.length)
return choice[randomIdx]
}
6️⃣ 승패 판정 로직 구현
사용자와 컴퓨터가 각각 가위, 바위, 보를 선택한 후, 사용자의 승패 판정
const determineWinner = (userChoice, computerChoice) => {
if (userChoice.type === computerChoice.type) return '비겼다'
if (
(userChoice.type === 'scissors' && computerChoice.type === 'paper') ||
(userChoice.type === 'rock' && computerChoice.type === 'scissors') ||
(userChoice.type === 'paper' && computerChoice.type === 'rock')
) {
return '이겼다'
} else {
return '졌다'
}
}
7️⃣ 사용자 버튼 선택 이벤트 처리
- handleUserChoice: 게임 시작 + 카운트다운 준비
- useEffect: 카운트다운 진행 ➡️ 카운트 0이면 컴퓨터 선택 + 승패 결정
- setTimeout으로 1초마다 카운트를 1씩 줄여나감
const handleUserChoice = userSelected => {
if (isPlaying) return // 이미 게임이 진행 중이면 무시
// 이전 기록 초기화
setUserChoice(null)
setComputerChoice(null)
setResult('?')
setIsPlaying(true) // 게임 진행 중으로 변경
setUserChoice(userSelected) // 사용자 선택 저장
setCount(3) // 카운트다운 시작 (3초)
}
useEffect(() => {
if (count === null) return
// 카운트가 0이 되면
if (count === 0) {
const computerSelected = generateComputerChoice(choice) // 컴퓨터 선택 생성
setComputerChoice(computerSelected) // 컴퓨터 선택 저장
const gameResult = determineWinner(userChoice, computerSelected) // 승부 결과 계산
setResult(gameResult) // 결과 저장
setIsPlaying(false) // 게임 종료 상태로 변경
setCount(null) // 카운트 리셋
return
}
// 타이머 (카운트가 0이 아닐 때, 1초마다 count를 1 감소)
const timer = setTimeout(() => {
setCount(prev => prev - 1)
}, 1000)
// 클린업 함수 (타이머 정리)
return () => clearTimeout(timer)
}, [count, userChoice, choice])
8️⃣ 결과 뒤집는 함수 구현
"사용자" 기준의 결과를 뒤집어서 "컴퓨터" 결과를 만들어내기 위함
const reverseResult = result => {
if (result === '이겼다') return '졌다'
if (result === '졌다') return '이겼다'
if (result === '비겼다') return '비겼다'
return ''
}
9️⃣ 다시하기 버튼을 통한 초기화 구현
게임을 초기 상태로 리셋 (사용자/컴퓨터 선택 및 결과 초기화)
const handleRetry = () => {
setUserChoice(null)
setComputerChoice(null)
setResult('?')
}
{(userChoice || computerChoice) && (
<button
className={`${styles['btn--retry']} ${styles['btn__con--common']}`}
onClick={handleRetry}
disabled={isPlaying}
>
다시하기
</button>
)}
🔟 UI 세부 스타일링
- 카운트가 시작되면 숫자가 커졌다가 작아지는 애니메이션 효과 주기
/* ... 생략 */
.count {
text-align: center;
font-size: 3rem;
font-weight: bold;
padding: var(--gap2) 0;
animation: scaleUp 0.5s ease-in-out;
}
@keyframes scaleUp {
0% {
transform: scale(1);
opacity: 0.5;
}
50% {
transform: scale(1.5);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0.5;
}
}
🪄 완성 코드
https://github.com/nue-os/rps-game
GitHub - nue-os/rps-game: 가위바위보 게임 ✌🏻✊🏻✋🏻
가위바위보 게임 ✌🏻✊🏻✋🏻 . Contribute to nue-os/rps-game development by creating an account on GitHub.
github.com
728x90
반응형
'LG 유플러스 유레카 SW > React' 카테고리의 다른 글
[#53] React 쇼핑몰 - 헤더 반응형으로 만들어보기 (1) | 2025.04.14 |
---|---|
[#52] React Router (2) | 2025.04.11 |
[#50] React - useState (5) | 2025.04.09 |
[#49] React 설정 (1) | 2025.04.08 |
[#46] React 컴포넌트, props, state (1) | 2025.04.02 |