-
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주차 기본/심화/공유 과제] React 연습 #6
base: main
Are you sure you want to change the base?
Changes from all commits
13676f4
226098d
db03782
f0f2fef
c8e1484
da0323e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
node_modules | ||
dist | ||
dist-ssr | ||
*.local | ||
|
||
# Editor directories and files | ||
.vscode/* | ||
!.vscode/extensions.json | ||
.idea | ||
.DS_Store | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
*.sw? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# React + Vite | ||
|
||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. | ||
|
||
Currently, two official plugins are available: | ||
|
||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh | ||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite + React</title> | ||
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. 제목도 설정하면 좀 더 좋을거 같아요~ |
||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<div id="modal-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,32 @@ | ||
{ | ||
"name": "week3", | ||
"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", | ||
"react-router-dom": "^6.27.0", | ||
"styled-components": "^6.1.13" | ||
}, | ||
"devDependencies": { | ||
"@eslint/js": "^9.13.0", | ||
"@types/react": "^18.3.11", | ||
"@types/react-dom": "^18.3.1", | ||
"@vitejs/plugin-react": "^4.3.3", | ||
"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.9" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#root { | ||
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. 프로젝트 처음 세팅을 할때 기본으로 먹혀있는 이런 App.css, index.css 파일은 삭제해서 스타일을 초기화 시켜주고, 시작하는게 좋습니다! 추가적인 스타일을 먹였을때 예상하지 못한대로 동작할 수 있기 때문이예요! 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. 오호.. 저도 처음에 이 파일이 있는지 모르고 원하는 바와 다르게 화면이 나와서 root를 어디서 설정하고 있는지 한참 찾았거든요.. |
||
max-width: 1280px; | ||
margin: 0 auto; | ||
padding: 2rem; | ||
text-align: center; | ||
} | ||
|
||
.logo { | ||
height: 6em; | ||
padding: 1.5em; | ||
will-change: filter; | ||
transition: filter 300ms; | ||
} | ||
.logo:hover { | ||
filter: drop-shadow(0 0 2em #646cffaa); | ||
} | ||
.logo.react:hover { | ||
filter: drop-shadow(0 0 2em #61dafbaa); | ||
} | ||
|
||
@keyframes logo-spin { | ||
from { | ||
transform: rotate(0deg); | ||
} | ||
to { | ||
transform: rotate(360deg); | ||
} | ||
} | ||
|
||
@media (prefers-reduced-motion: no-preference) { | ||
a:nth-of-type(2) .logo { | ||
animation: logo-spin infinite 20s linear; | ||
} | ||
} | ||
|
||
.card { | ||
padding: 2em; | ||
} | ||
|
||
.read-the-docs { | ||
color: #888; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// import { useState } from 'react' | ||
import './App.css' | ||
import ProfileCard from './component/ProfileCard' | ||
import Game from './pages/games/game' | ||
import GlobalStyle from './styles/global'; | ||
|
||
function App() { | ||
|
||
return ( | ||
<> | ||
<GlobalStyle /> | ||
{/* <ProfileCard /> */} | ||
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. 사용하지 않는 주석들은 삭제하기! |
||
<Game /> | ||
</> | ||
) | ||
} | ||
|
||
export default App |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { useState } from 'react' | ||
import { members } from "../data/member"; | ||
import styled from "@emotion/styled" | ||
|
||
// 여기 컴포넌트에는 카드 형식만 만들고 App.jsx나 페이지에서 정보 넣고 돌리기 | ||
|
||
const ProfileCard = ({ }) => { | ||
|
||
return ( | ||
<MainContainer> | ||
{members.map((k)=>{ | ||
const [count, setCount] = useState(0) | ||
|
||
return( | ||
<Container> | ||
<div key={k.id}> | ||
<MemberName>{k.name}</MemberName> | ||
<MemberInfo>{k.englishName}</MemberInfo> | ||
<MemberInfo>{k.github}</MemberInfo> | ||
<Btn> | ||
<div>{count}</div> | ||
<button onClick={() => setCount((count) => count + 1)}> | ||
Like | ||
</button> | ||
</Btn> | ||
</div> | ||
</Container> | ||
) | ||
|
||
})} | ||
</MainContainer> | ||
) | ||
} | ||
|
||
export default ProfileCard; | ||
|
||
|
||
|
||
const MainContainer= styled.div` | ||
display : flex ; | ||
justify-content: center; | ||
flex-wrap: wrap; | ||
gap:1rem; | ||
`; | ||
|
||
const Container = styled.div` | ||
width:10rem; | ||
height:10rem; | ||
background-color: #b0f0c6; | ||
border-radius: 1rem; | ||
;` | ||
|
||
const MemberName = styled.div` | ||
font-size:1.5rem; | ||
padding-top: 1rem; | ||
;` | ||
|
||
const MemberInfo = styled.div` | ||
font-size:1rem; | ||
;` | ||
|
||
const Btn = styled.div` | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
gap:1rem; | ||
margin-top:0.5rem; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import React, { useState, useEffect } from 'react'; | ||
import * as S from './styled'; | ||
import Modal from './modal'; | ||
|
||
function GameBoard({ startTimer, stopTimer, resetTimer }) { | ||
const [cards, setCards] = useState([]); | ||
const [clickedNumbers, setClickedNumbers] = useState(new Set()); | ||
const [currentClick, setCurrentClick] = useState(1); | ||
const [usedNumbers, setUsedNumbers] = useState(new Set()); | ||
const [isGameOver, setIsGameOver] = useState(false); | ||
const [time, setTime] = useState(0); | ||
const [intervalId, setIntervalId] = useState(null); | ||
const [currentLevel, setCurrentLevel] = useState(1); | ||
|
||
const startCards = () => { | ||
const initialNumbers = Array.from({ length: 9 }, (_, i) => i + 1); | ||
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. Level2,3을 구현할때 level의 value값을 prop으로 받아서 +2를 해주면 카드 배열의 한 줄 개수를 알 수 있고, 이를 제곱하면 카드의 총 개수를 알 수 있으며, 여기서 *2 해주면 필요한 총 숫자 개수까지 알 수 있을 것 같아요. 이걸 변수로 설정해서 length: 9 이 부분을 동적으로 설정해줄 수 있겠네요! 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. threeGrid, createNewCard 함수 등 Level1의 개수와 관련된 부분을 모두 이런 방식으로 수정해주면 될 것 같습니다! |
||
const mixNumbers = initialNumbers.sort(() => Math.random() - 0.5); | ||
|
||
const threeGrid = []; | ||
for (let i = 0; i < 9; i += 3) { | ||
threeGrid.push(mixNumbers.slice(i, i + 3)); | ||
} | ||
|
||
setCards(threeGrid); | ||
}; | ||
|
||
const createNewCard = () => { | ||
let randomAfterNumber; | ||
do { | ||
randomAfterNumber = Math.floor(Math.random() * 9) + 10; | ||
} while (usedNumbers.has(randomAfterNumber)); | ||
Comment on lines
+29
to
+31
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. do while 문을 실제로 쓴건 처음보네요. while문과의 차이가 무조건 do {} 부분을 한번 실행하고 반복을 결정한다고 하는데 의도하신 부분일까요? |
||
|
||
setUsedNumbers((prev) => new Set(prev).add(randomAfterNumber)); | ||
return randomAfterNumber; | ||
}; | ||
|
||
const handleCardClick = (num, rowIndex, colIndex) => { | ||
if (num === currentClick) { | ||
if (currentClick === 1) { | ||
const newIntervalId = setInterval(() => setTime(prev => prev + 0.01), 10); | ||
setIntervalId(newIntervalId); | ||
startTimer(); | ||
} | ||
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. setIntervalId로 타이머의 ID값을 저장해서 사용하는 방식이 생각지 못한 부분이었어요!! |
||
|
||
setClickedNumbers((prev) => new Set(prev).add(num)); | ||
|
||
if (num >= 10) { | ||
const updatedCards = cards.map((row, rIdx) => | ||
row.map((card, cIdx) => (rIdx === rowIndex && cIdx === colIndex ? '' : card)) | ||
); | ||
setCards(updatedCards); | ||
} else { | ||
const randomAfterNumber = createNewCard(); | ||
const updatedCards = cards.map((row, rIdx) => | ||
row.map((card, cIdx) => (rIdx === rowIndex && cIdx === colIndex ? randomAfterNumber : card)) | ||
); | ||
setCards(updatedCards); | ||
} | ||
|
||
setCurrentClick(prev => prev + 1); | ||
|
||
if (currentClick === 18) { | ||
clearInterval(intervalId); | ||
stopTimer(); | ||
setIsGameOver(true); | ||
} | ||
} else { | ||
alert("틀렸어요🥲\n" + currentClick + "를 클릭하세요 !"); | ||
|
||
} | ||
}; | ||
|
||
const closeModal = () => { | ||
const currentTime = new Date().toLocaleString(); | ||
const existingData = JSON.parse(localStorage.getItem('gameData')) || []; | ||
existingData.push({ | ||
currentTime, | ||
level: currentLevel, | ||
playTime: time.toFixed(2) | ||
}); | ||
localStorage.setItem('gameData', JSON.stringify(existingData)); | ||
Comment on lines
+74
to
+81
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. 모달을 닫는 함수에 있어서 로컬스토리지에 시간 같은 데이터를 저장하는 로직은 필요한 로직이지만 약간 분리시켜주는게 좋을 것 같아요. util함수로 분리하면 코드도 훨씬 깔끔해지고, 도메인을 분리할 수 있을 것 같습니다! |
||
|
||
setIsGameOver(false); | ||
setTime(0); | ||
setCurrentClick(1); | ||
setClickedNumbers(new Set()); | ||
setUsedNumbers(new Set()); | ||
resetTimer(); | ||
startCards(); | ||
}; | ||
|
||
useEffect(() => { | ||
startCards(); | ||
}, []); | ||
|
||
return ( | ||
<S.NextNumber>다음 숫자 : {currentClick} | ||
<S.Board> | ||
{cards.map((row, rowIndex) => ( | ||
<S.Row key={rowIndex}> | ||
{row.map((num, colIndex) => ( | ||
<S.Col key={colIndex} onClick={() => handleCardClick(num, rowIndex, colIndex)}> | ||
{num} | ||
</S.Col> | ||
))} | ||
</S.Row> | ||
))} | ||
<Modal isOpen={isGameOver} onClose={closeModal} time={time} /> | ||
</S.Board> | ||
</S.NextNumber> | ||
); | ||
} | ||
|
||
export default GameBoard; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import React, { useState } from 'react'; | ||
import * as S from "./styled"; | ||
|
||
function Header({ activeButton, setActiveButton, time, resetTimer }) { | ||
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. 왜 화살표 함수로 컴포넌트를 생성하지 않고 function으로 했는지 알 수 있을까요?? |
||
const [showHeaderRight, setShowHeaderRight] = useState(true); | ||
|
||
const handleButtonClick = (button) => { | ||
setActiveButton(button); | ||
setShowHeaderRight(button === "game"); | ||
}; | ||
|
||
return ( | ||
<S.Header> | ||
<S.HeaderLeft> | ||
<S.HeaderMent>1 to 50</S.HeaderMent> | ||
<S.ButtonGame | ||
onClick={() => handleButtonClick("game")} | ||
isActive={activeButton === "game"} | ||
> | ||
게임 | ||
</S.ButtonGame> | ||
<S.ButtonRanking | ||
onClick={() => handleButtonClick("ranking")} | ||
isActive={activeButton === "ranking"} | ||
> | ||
랭킹 | ||
</S.ButtonRanking> | ||
</S.HeaderLeft> | ||
{showHeaderRight && ( | ||
<S.HeaderRight> | ||
<S.Dropdown> | ||
<select> | ||
<option value="1">Level 1</option> | ||
<option value="2">Level 2</option> | ||
<option value="3">Level 3</option> | ||
</select> | ||
</S.Dropdown> | ||
<S.Timer>{time}초</S.Timer> | ||
</S.HeaderRight> | ||
)} | ||
</S.Header> | ||
); | ||
} | ||
|
||
export default Header; |
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.
ko로 바꾸기~