-
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 게임 #4
base: main
Are you sure you want to change the base?
Changes from all commits
e45bae9
9b62390
a37fb39
c2aed6f
b8f4e10
4b832f4
54aa84c
8c7346c
d8e3bdc
46b732c
0a84a38
031eb79
9df757c
5bf7afe
c5a9fd3
fd5c095
7f29a10
a6e8d5d
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,39 @@ | ||
import js from "@eslint/js"; | ||
import globals from "globals"; | ||
import react from "eslint-plugin-react"; | ||
import reactHooks from "eslint-plugin-react-hooks"; | ||
import reactRefresh from "eslint-plugin-react-refresh"; | ||
|
||
export default [ | ||
{ ignores: ["dist"] }, | ||
{ | ||
files: ["**/*.{js,jsx}"], | ||
languageOptions: { | ||
ecmaVersion: 2020, | ||
globals: globals.browser, | ||
parserOptions: { | ||
ecmaVersion: "latest", | ||
ecmaFeatures: { jsx: true }, | ||
sourceType: "module", | ||
}, | ||
}, | ||
settings: { react: { version: "18.3" } }, | ||
plugins: { | ||
react, | ||
"react-hooks": reactHooks, | ||
"react-refresh": reactRefresh, | ||
}, | ||
rules: { | ||
...js.configs.recommended.rules, | ||
...react.configs.recommended.rules, | ||
...react.configs["jsx-runtime"].rules, | ||
...reactHooks.configs.recommended.rules, | ||
"react/jsx-no-target-blank": "off", | ||
"react-refresh/only-export-components": [ | ||
"warn", | ||
{ allowConstantExport: true }, | ||
], | ||
"react/prop-types": "off", | ||
}, | ||
}, | ||
]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<!DOCTYPE html> | ||
<html lang="ko"> | ||
<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>Number Game</title> | ||
</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,30 @@ | ||
{ | ||
"name": "number-game", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "vite build", | ||
"lint": "eslint .", | ||
"preview": "vite preview" | ||
}, | ||
"dependencies": { | ||
"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.12", | ||
"@types/react-dom": "^18.3.1", | ||
"@vitejs/plugin-react-swc": "^3.5.0", | ||
"eslint": "^9.13.0", | ||
"eslint-plugin-react": "^7.37.2", | ||
"eslint-plugin-react-hooks": "^5.0.0", | ||
"eslint-plugin-react-refresh": "^0.4.14", | ||
"globals": "^15.11.0", | ||
"vite": "^5.4.10" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import GlobalStyle from "../styles/GlobalStyle"; | ||
import { ThemeProvider } from "styled-components"; | ||
import theme from "../styles/theme"; | ||
import Router from "./Router"; | ||
|
||
function App() { | ||
return ( | ||
<ThemeProvider theme={theme}> | ||
<GlobalStyle /> | ||
<Router /> | ||
</ThemeProvider> | ||
); | ||
} | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { BrowserRouter, Route, Routes } from "react-router-dom"; | ||
import { Home } from "./pages/Home"; | ||
|
||
function Router() { | ||
return ( | ||
<BrowserRouter> | ||
<Routes> | ||
<Route path="/" element={<Home />} /> | ||
</Routes> | ||
</BrowserRouter> | ||
); | ||
} | ||
|
||
export default Router; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { useState } from "react"; | ||
import styled, { keyframes, css } from "styled-components"; | ||
import CompleteModal from "../../Modal/CompleteModal"; | ||
import NextNumber from "./NextNumber"; | ||
import { generateShuffledNumbers } from "../../../utils/generateShuffledNumbers"; | ||
import { resetGame } from "../../../utils/resetGame"; | ||
import { handleNumberClick } from "../../../utils/handleNumberClick"; | ||
|
||
const GameBoard = ({ | ||
initialLength, | ||
remainingLength, | ||
gridLength, | ||
onFirstClick, | ||
onLastClick, | ||
onReset, | ||
currentTime, | ||
}) => { | ||
const [initialNums, setInitialnums] = useState(() => | ||
generateShuffledNumbers(1, initialLength) | ||
); | ||
const [upcomingNums, setUpcomingNums] = useState(() => | ||
generateShuffledNumbers(initialLength + 1, remainingLength) | ||
); | ||
const [currentNumber, setCurrentNumber] = useState(1); | ||
const [gameComplete, setGameComplete] = useState(false); | ||
const [clickedIndexes, setClickedIndexes] = useState(new Set()); | ||
|
||
const handleInitialNumClick = (index) => { | ||
const [next, ...rest] = upcomingNums; | ||
setUpcomingNums(rest); | ||
setInitialnums((prev) => prev.map((num, i) => (i === index ? next : num))); | ||
setClickedIndexes((prev) => new Set(prev).add(index)); | ||
setCurrentNumber((prev) => prev + 1); | ||
}; | ||
|
||
const handleUpcomingNumClick = (index) => { | ||
setInitialnums((prev) => prev.map((num, i) => (i === index ? null : num))); | ||
setCurrentNumber((prev) => prev + 1); | ||
}; | ||
|
||
const reset = () => { | ||
resetGame(initialLength, remainingLength, { | ||
setInitialnums, | ||
setUpcomingNums, | ||
setCurrentNumber, | ||
setGameComplete, | ||
setClickedIndexes, | ||
onReset, | ||
}); | ||
}; | ||
Comment on lines
+41
to
+50
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. p3: 이번에 제 과제를 할 때 시간 투자를 많이 못해서 함수 분리(utils나 custom hook 등)를 거의 못해서.. 이런 리뷰를 드리기 조금 죄송스럽지만 이에 대해서 어떤 의견을 가지고 계신지 궁금합니다! |
||
|
||
const handleClick = (number, index) => { | ||
handleNumberClick({ | ||
number, | ||
currentNumber, | ||
initialLength, | ||
remainingLength, | ||
handleInitialNumClick: () => handleInitialNumClick(index), | ||
handleUpcomingNumClick: () => handleUpcomingNumClick(index), | ||
setGameComplete, | ||
onFirstClick, | ||
onLastClick, | ||
}); | ||
}; | ||
|
||
const handleCloseModal = () => { | ||
reset(); | ||
}; | ||
|
||
return ( | ||
<GameBoardContainer> | ||
<NextNumber currentNumber={currentNumber} /> | ||
<GameBox $gridLength={gridLength}> | ||
{initialNums.map((number, index) => | ||
number !== null ? ( | ||
<NumberButton | ||
key={index} | ||
onClick={() => handleClick(number, index)} | ||
disabled={number < currentNumber} | ||
isClicked={clickedIndexes.has(index)} | ||
> | ||
{number} | ||
</NumberButton> | ||
) : ( | ||
<EmptySpace key={index} /> | ||
) | ||
Comment on lines
+84
to
+86
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. p1: 여기서는 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.
맞네요! 감사합니다 |
||
)} | ||
Comment on lines
+73
to
+87
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. p3: |
||
</GameBox> | ||
{gameComplete && ( | ||
<CompleteModal onClose={handleCloseModal}> | ||
<Message>게임 기록: {currentTime}</Message> | ||
<CloseButton onClick={handleCloseModal}>닫기</CloseButton> | ||
</CompleteModal> | ||
)} | ||
</GameBoardContainer> | ||
Comment on lines
+89
to
+95
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. p3: 여기서 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.
처음에 내부 모달 내부 요소들을 |
||
); | ||
}; | ||
|
||
export default GameBoard; | ||
|
||
const GameBoardContainer = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
`; | ||
|
||
const GameBox = styled.div` | ||
display: grid; | ||
grid-template-columns: repeat(${(props) => props.$gridLength}, 1fr); | ||
gap: 1rem; | ||
padding: 2rem; | ||
`; | ||
|
||
const blink = keyframes` | ||
0%, 100% { opacity: 1; } | ||
50% { opacity: 0.5; } | ||
`; | ||
|
||
const NumberButton = styled.button.withConfig({ | ||
shouldForwardProp: (prop) => prop !== "isClicked", | ||
})` | ||
Comment on lines
+120
to
+122
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. p3:
Comment on lines
+120
to
+122
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. p3: 이렇게 작성해도 styled-components 스타일링을 위해 props는 전달되지만 실제 DOM 요소에는 전달되지 않는군요!!! 단순히 저는 |
||
width: 10rem; | ||
height: 10rem; | ||
font-size: 3rem; | ||
font-weight: 700; | ||
border-radius: 10px; | ||
background-color: ${({ isClicked, theme }) => | ||
isClicked ? theme.colors.thumnail : theme.colors.orange1}; | ||
transition: background-color 0.5s; | ||
${({ isClicked }) => | ||
isClicked && | ||
css` | ||
animation: ${blink} 0.3s; | ||
`} | ||
`; | ||
|
||
const EmptySpace = styled.div` | ||
width: 10rem; | ||
height: 10rem; | ||
background-color: transparent; | ||
visibility: hidden; | ||
`; | ||
|
||
const Message = styled.div` | ||
font-size: 3rem; | ||
font-weight: 700; | ||
color: ${({ theme }) => theme.colors.black1}; | ||
`; | ||
|
||
const CloseButton = styled.button` | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
width: 10rem; | ||
height: 5rem; | ||
background-color: ${({ theme }) => theme.colors.black1}; | ||
color: ${({ theme }) => theme.colors.white1}; | ||
border: none; | ||
padding: 0.5rem 1rem; | ||
border-radius: 5px; | ||
cursor: pointer; | ||
|
||
&:hover { | ||
background-color: ${({ theme }) => theme.colors.gray1}; | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import styled from "styled-components"; | ||
|
||
const NextNumber = ({ currentNumber }) => { | ||
return <NextNumberContainer>다음 숫자: {currentNumber}</NextNumberContainer>; | ||
}; | ||
|
||
export default NextNumber; | ||
|
||
const NextNumberContainer = styled.div` | ||
font-size: 3.5rem; | ||
font-weight: 900; | ||
color: ${({ theme }) => theme.colors.black1}; | ||
`; |
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.
p3:
gameSelectLevel로 아예 레벨 선택 로직을 유틸로 분리해두신 게 흥미롭네요,,!!
유틸 활용을 잘 하고 계셔서 코드가 더 재사용성이 높은 것 같아요!! 배워갑니다 ㅎㅎ