-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[3주차 기본/심화/공유 과제] 1 to 50 게임 #6
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!DOCTYPE html> | ||
<html lang="ko"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Click the Numbers Game</title> | ||
<link rel="stylesheet" href="./index.css" /> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="/src/main.jsx"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "week3_practice", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "vite build", | ||
"lint": "eslint .", | ||
"preview": "vite preview" | ||
}, | ||
"dependencies": { | ||
"@emotion/react": "^11.13.3", | ||
"@emotion/styled": "^11.13.0", | ||
"react": "^18.3.1", | ||
"react-dom": "^18.3.1" | ||
}, | ||
"devDependencies": { | ||
"@eslint/js": "^9.13.0", | ||
"@types/react": "^18.3.11", | ||
"@types/react-dom": "^18.3.1", | ||
"@vitejs/plugin-react-swc": "^3.5.0", | ||
"eslint": "^9.13.0", | ||
"eslint-plugin-react": "^7.37.1", | ||
"eslint-plugin-react-hooks": "^5.0.0", | ||
"eslint-plugin-react-refresh": "^0.4.13", | ||
"globals": "^15.11.0", | ||
"vite": "^5.4.10" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
.app { | ||
text-align: center; | ||
margin-top: 80px; | ||
} | ||
|
||
.tab-container { | ||
display: flex; | ||
justify-content: center; | ||
margin-top: 20px; | ||
} | ||
|
||
.tab-container button { | ||
padding: 10px 20px; | ||
font-size: 1em; | ||
cursor: pointer; | ||
margin: 0 5px; | ||
border: none; | ||
border-radius: 5px; | ||
} | ||
|
||
.tab-container .active { | ||
background-color: #4caf50; | ||
color: white; | ||
} | ||
|
||
.next-number { | ||
font-weight: bold; | ||
padding: 30px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// src/App.jsx | ||
|
||
import React, { useState, useRef, useEffect } from 'react'; | ||
import Header from './components/Header'; | ||
import Board from './components/Board'; | ||
import RankingBoard from './components/RankingBoard'; | ||
import './App.css'; | ||
|
||
const App = () => { | ||
const [selectedTab, setSelectedTab] = useState('game'); // 'game' 또는 'ranking' | ||
Comment on lines
+9
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호! 이렇게 주석으로 어떤 어떤 탭이 있는지 설명해주는 것 너무 좋은 것 같습니다 ㅎㅎ |
||
const [isGameStarted, setIsGameStarted] = useState(false); | ||
const [isGameCompleted, setIsGameCompleted] = useState(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 여기 |
||
const [time, setTime] = useState(0); | ||
const [nextNumber, setNextNumber] = useState(1); | ||
const timerRef = useRef(null); | ||
Comment on lines
+13
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. useRef를 사용해서 불필요한 리렌더링을 방지하는 코드 너무 좋은 것 같습니다!! 최고최고👍🏻👍🏻 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. setInterval의 리렌더링만 방지하고 있어서 타이머의 상태가 바뀔 때 발생하는 리렌더링은 방지하지 못하고 있어요...! 이 부분도 방지하면 더 좋을 것 같습니다 :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ㄷ ㄷ 레전드 개발자 탄생 |
||
|
||
useEffect(() => { | ||
if (isGameStarted) { | ||
timerRef.current = setInterval(() => { | ||
setTime((prevTime) => prevTime + 0.01); | ||
}, 10); | ||
} else { | ||
clearInterval(timerRef.current); // 타이머 정지 | ||
} | ||
|
||
|
||
return () => clearInterval(timerRef.current); | ||
}, [isGameStarted]); | ||
|
||
const startGame = () => { | ||
setTime(0); // 타이머 초기화 | ||
setNextNumber(1); | ||
setIsGameStarted(false); | ||
setIsGameCompleted(false); | ||
}; | ||
|
||
Comment on lines
+29
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 함수는 선언만 되어있고 호출은 안하고 있는데 선언하신 궁금합니다!! |
||
const startTimerOnFirstClick = () => { | ||
if (!isGameStarted) { | ||
setIsGameStarted(true); // 첫 숫자를 누를 때 타이머 시작 | ||
} | ||
}; | ||
|
||
const completeGame = () => { | ||
setIsGameStarted(false); | ||
setIsGameCompleted(true); | ||
|
||
// 게임 완료 시 로컬 스토리지에 기록 저장 | ||
const gameData = { | ||
date: new Date().toLocaleString(), | ||
level: "레벨 1", | ||
playTime: time.toFixed(2), | ||
}; | ||
localStorage.setItem(`gameResult_${Date.now()}`, JSON.stringify(gameData)); | ||
|
||
alert(`성공! ${time.toFixed(2)}초 소요`); | ||
}; | ||
|
||
const resetGame = () => { | ||
setIsGameStarted(false); | ||
setTime(0); | ||
setNextNumber(1); | ||
setIsGameCompleted(false); | ||
}; | ||
|
||
const handleTabChange = (tab) => { | ||
resetGame(); | ||
setSelectedTab(tab); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Header | ||
selectedTab={selectedTab} | ||
onTabChange={handleTabChange} | ||
time={time} | ||
isGameStarted={isGameStarted} | ||
/> | ||
<div className="app"> | ||
{selectedTab === 'game' && ( | ||
<div className="game-container"> | ||
<div className="next-number">다음 숫자: {nextNumber}</div> | ||
<Board | ||
nextNumber={nextNumber} | ||
onCorrectClick={() => { | ||
startTimerOnFirstClick(); // 첫 숫자 클릭 시 타이머 시작 | ||
setNextNumber((prev) => prev + 1); | ||
}} | ||
onGameComplete={completeGame} | ||
/> | ||
</div> | ||
)} | ||
{selectedTab === 'ranking' && <RankingBoard />} | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.board { | ||
display: grid; | ||
gap: 10px; | ||
grid-template-columns: repeat(3, 80px); | ||
grid-template-rows: repeat(3, 80px); | ||
justify-content: center; | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,48 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import React, { useState, useEffect } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import Tile from './Tile'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import './Board.css'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
const Board = ({ nextNumber, onCorrectClick, onGameComplete }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [numbers, setNumbers] = useState([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
initializeBoard(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
const initializeBoard = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const initialNumbers = Array.from({ length: 9 }, (_, i) => i + 1); // 1 to 9 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
shuffleArray(initialNumbers); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
setNumbers(initialNumbers); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
const shuffleArray = (array) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
for (let i = array.length - 1; i > 0; i--) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const j = Math.floor(Math.random() * (i + 1)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
[array[i], array[j]] = [array[j], array[i]]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+12
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shuffleArray에서 initialNumbers라는 배열의 값을 직접 변경해주면서 불변성이 지켜지지 않고 있습니다..! React에서는 불변성을 지키는 것이 중요합니다. 따라서 아래처럼 코드를 수정해서 불변성을 지키는 것은 어떨까요??ㅎㅎ
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 리액트 첫 과제 했을때 불변성에 대해 코드리뷰 받았었는데, 온전히 이해하기 어려웠던 기억이 새록새록하네요,,!💜🫧 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
const handleTileClick = (number) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (number === nextNumber) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (number === 9) { // 9가 마지막 숫자 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
onGameComplete(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
onCorrectClick(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="board"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
{numbers.map((number) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Tile | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
key={number} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
number={number} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
onClick={() => handleTileClick(number)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default Board; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
.header { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
padding: 10px 20px; | ||
background-color: #4caf50; | ||
color: white; | ||
position: fixed; | ||
top: 0; | ||
left: 0; | ||
width: 100%; | ||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); | ||
} | ||
|
||
.tabs { | ||
display: flex; | ||
gap: 10px; | ||
} | ||
|
||
.tabs button { | ||
padding: 10px 20px; | ||
font-size: 1em; | ||
cursor: pointer; | ||
background: none; | ||
border: none; | ||
color: white; | ||
} | ||
|
||
.tabs .active { | ||
background-color: white; | ||
color: #4caf50; | ||
border-radius: 5px; | ||
} | ||
|
||
.header-right { | ||
display: flex; | ||
gap: 10px; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import React from 'react'; | ||
import './Header.css'; | ||
|
||
const Header = ({ selectedTab, onTabChange, time }) => { | ||
return ( | ||
<header className="header"> | ||
<span>1 to 50</span> | ||
<div className="tabs"> | ||
<button | ||
onClick={() => onTabChange('game')} | ||
className={selectedTab === 'game' ? 'active' : ''} | ||
> | ||
게임 | ||
</button> | ||
<button | ||
onClick={() => onTabChange('ranking')} | ||
className={selectedTab === 'ranking' ? 'active' : ''} | ||
> | ||
랭킹 | ||
</button> | ||
</div> | ||
{selectedTab === 'game' && ( | ||
<div className="header-right"> | ||
<select> | ||
<option>레벨 1</option> | ||
<option>레벨 2</option> | ||
<option>레벨 3</option> | ||
</select> | ||
<span>타이머: {time.toFixed(2)} 초</span> | ||
</div> | ||
)} | ||
</header> | ||
); | ||
}; | ||
|
||
export default Header; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
.ranking-board { | ||
margin-top: 20px; | ||
padding: 20px; | ||
background-color: #ffffff; | ||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | ||
border-radius: 8px; | ||
} | ||
|
||
.ranking-header { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
margin-bottom: 15px; | ||
} | ||
|
||
.reset-button { | ||
padding: 5px 10px; | ||
font-size: 0.9em; | ||
color: #4caf50; | ||
background-color: #fff; | ||
border: 1px solid #4caf50; | ||
border-radius: 5px; | ||
cursor: pointer; | ||
transition: background-color 0.2s, color 0.2s; | ||
} | ||
|
||
.reset-button:hover { | ||
background-color: #4caf50; | ||
color: white; | ||
} | ||
|
||
.ranking-board table { | ||
width: 100%; | ||
border-collapse: collapse; | ||
} | ||
|
||
.ranking-board th, | ||
.ranking-board td { | ||
padding: 10px; | ||
text-align: left; | ||
border-bottom: 1px solid #ddd; | ||
} | ||
|
||
.ranking-board th { | ||
background-color: #4caf50; | ||
color: white; | ||
} | ||
|
||
.ranking-board td { | ||
color: #555; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기 파일명이
app.jsx
로 되어있는데요!일반적으로 React 컴포넌트 파일 이름은 대문자로 시작해서 파스칼 케이스에 맞게 작성합니다!!
크게 문제가 되는 것은 아니지만,
App.jsx
로 이름을 지어서 컴포넌트라는 것을 쉽게 알 수 있도록 하는 것을 추천드립니다 :)