-
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주차 기본/심화/공유 과제] 숫자 게임 만들기 🔢 #4
base: main
Are you sure you want to change the base?
Changes from all commits
40473ef
eb09578
d60c928
581e134
b4419f8
0bf5513
476780d
ecbf7a9
2f456d5
fffd33d
16040d9
74d1696
f170fb6
d424d98
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,13 @@ | ||
<!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> | ||
</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": "assignment", | ||
"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.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 { Global, ThemeProvider } from "@emotion/react"; | ||
import GlobalStyle from "./styles/global"; | ||
import theme from "./styles/theme"; | ||
import Game from "./pages/Game"; | ||
|
||
function App() { | ||
return ( | ||
<ThemeProvider theme={theme}> | ||
<Global styles={GlobalStyle} /> | ||
<Game /> | ||
</ThemeProvider> | ||
); | ||
} | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import styled from "@emotion/styled"; | ||
import theme from "../styles/theme"; | ||
import { useState } from "react"; | ||
import { formatTime } from "../utils/format"; | ||
|
||
const Header = ({ handleChangeContent, content, time }) => { | ||
const [buttonState, setButtonState] = useState("GAME"); | ||
|
||
const handleClickButton = (content) => { | ||
handleChangeContent(content); | ||
setButtonState(content); | ||
}; | ||
|
||
return ( | ||
<HeaderLayout> | ||
<HeaderContainer> | ||
<TitleStyle>1 to 50</TitleStyle> | ||
<ButtonWrapper> | ||
<GameButtonStyle | ||
buttonState={buttonState} | ||
onClick={() => handleClickButton("GAME")} | ||
> | ||
게임 | ||
</GameButtonStyle> | ||
<RankingButtonStyle | ||
buttonState={buttonState} | ||
onClick={() => handleClickButton("RANKING")} | ||
> | ||
랭킹 | ||
</RankingButtonStyle> | ||
</ButtonWrapper> | ||
</HeaderContainer> | ||
|
||
{content === "GAME" && ( | ||
<HeaderContainer> | ||
<SelectWrapper> | ||
<SelectStyle> | ||
<option>Level 1</option> | ||
<option>Level 2</option> | ||
<option>Level 3</option> | ||
</SelectStyle> | ||
</SelectWrapper> | ||
<TimerStyle>{formatTime(time)}</TimerStyle> | ||
</HeaderContainer> | ||
)} | ||
</HeaderLayout> | ||
); | ||
}; | ||
|
||
export default Header; | ||
|
||
const HeaderLayout = styled.header` | ||
display: flex; | ||
justify-content: space-between; | ||
width: 100%; | ||
padding: 1rem 5rem; | ||
background-color: ${theme.color.purple5}; | ||
`; | ||
|
||
const HeaderContainer = styled.section` | ||
display: flex; | ||
align-items: center; | ||
gap: 2rem; | ||
`; | ||
|
||
const TitleStyle = styled.h1` | ||
${theme.font.head} | ||
color: ${theme.color.white}; | ||
`; | ||
|
||
const ButtonWrapper = styled.div` | ||
display: flex; | ||
gap: 0.5rem; | ||
`; | ||
|
||
const GameButtonStyle = styled.button` | ||
${theme.font.subHead}; | ||
padding: 0.5rem 1rem; | ||
color: ${theme.color.white}; | ||
background-color: ${({ buttonState }) => | ||
buttonState === "GAME" ? theme.color.purple3 : theme.color.purple5}; | ||
border: none; | ||
border-radius: 5px; | ||
`; | ||
|
||
const RankingButtonStyle = styled.button` | ||
${theme.font.subHead}; | ||
padding: 0.5rem 1rem; | ||
color: ${theme.color.white}; | ||
background-color: ${({ buttonState }) => | ||
buttonState === "RANKING" ? theme.color.purple3 : theme.color.purple5}; | ||
border: none; | ||
border-radius: 5px; | ||
`; | ||
|
||
const SelectWrapper = styled.div` | ||
border-radius: 5px; | ||
padding: 0.5rem; | ||
background-color: ${theme.color.white}; | ||
`; | ||
|
||
const SelectStyle = styled.select` | ||
border: none; | ||
`; | ||
|
||
const TimerStyle = styled.span` | ||
${theme.font.subHead} | ||
color: ${theme.color.white}; | ||
width: 5rem; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import styled from "@emotion/styled"; | ||
import theme from "../styles/theme"; | ||
|
||
const NumberCard = ({ cardNumber, handleCardClick, isVisible }) => { | ||
return ( | ||
<> | ||
<NumberCardLayout | ||
onClick={() => handleCardClick(cardNumber)} | ||
isVisible={isVisible} | ||
> | ||
<NumberCardTextStyle>{cardNumber}</NumberCardTextStyle> | ||
</NumberCardLayout> | ||
</> | ||
); | ||
}; | ||
Comment on lines
+4
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. 오 카드도 컴포넌트로 해서 IsVisible 값으로 처리하는 게 깔끔하고 좋네요,,!👍🏻👍🏻 |
||
|
||
export default NumberCard; | ||
|
||
const NumberCardLayout = styled.div` | ||
width: 5rem; | ||
height: 5rem; | ||
background-color: ${theme.color.purple3}; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
visibility: ${({ isVisible }) => (!isVisible ? "hidden" : "visible")}; | ||
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. visibility 속성을 사용해서 슷자카드를 조정할 수 있네요!! 🤩 |
||
`; | ||
|
||
const NumberCardTextStyle = styled.span` | ||
color: ${theme.color.white}; | ||
${theme.font.head} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { useState } from "react"; | ||
import theme from "../styles/theme"; | ||
import NumberCard from "./NumberCard"; | ||
import styled from "@emotion/styled"; | ||
import { formatTime } from "../utils/format"; | ||
import { createRandomArray } from "../utils/createRandomArray"; | ||
|
||
const NumberGame = ({ | ||
startTimer, | ||
resetTimer, | ||
time, | ||
handleSetLocalStorage, | ||
}) => { | ||
const [cardNumber, setCardNumber] = useState(1); | ||
const [numberArray, setNumberArray] = useState(createRandomArray(1, 9)); | ||
const [nextNumberArray, setNextNumberArray] = useState( | ||
createRandomArray(10, 18) | ||
); | ||
const [isVisible, setIsVisible] = useState(Array(9).fill(true)); | ||
|
||
const renderNewRandomArray = () => { | ||
setNumberArray(createRandomArray(1, 9)); | ||
setNextNumberArray(createRandomArray(10, 18)); | ||
setIsVisible(Array(9).fill(true)); | ||
}; | ||
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 handleCardNumberChange = (number) => { | ||
setCardNumber(number); | ||
}; | ||
|
||
const handleCardClick = (number) => { | ||
if (number === 1) { | ||
startTimer(); | ||
} | ||
|
||
if (number === cardNumber) { | ||
handleCardNumberChange((prev) => prev + 1); | ||
Comment on lines
+27
to
+37
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 newNumberArray = [...numberArray]; | ||
const updateIsVisible = [...isVisible]; | ||
const numberCardIndex = newNumberArray.indexOf(number); | ||
|
||
if (number <= 9) { | ||
newNumberArray.splice(numberCardIndex, 1, nextNumberArray[number - 1]); | ||
setNumberArray(newNumberArray); | ||
} else { | ||
updateIsVisible.splice(numberCardIndex, 1, false); | ||
setIsVisible(updateIsVisible); | ||
} | ||
} | ||
|
||
// 수정해라. | ||
if (number === 18) { | ||
resetTimer(); | ||
handleSetLocalStorage(); | ||
alert(`게임 끝! 기록: ${formatTime(time)}초`); | ||
renderNewRandomArray(); | ||
handleCardNumberChange(1); | ||
} | ||
}; | ||
|
||
return ( | ||
<NumberGameLayout> | ||
<NumberBoardTextStyle> 다음 숫자: {cardNumber}</NumberBoardTextStyle> | ||
<NomberBoardContainer> | ||
{numberArray.map((number, index) => { | ||
return ( | ||
<NumberCard | ||
key={index} | ||
cardNumber={number} | ||
handleCardClick={handleCardClick} | ||
isVisible={isVisible[index]} | ||
/> | ||
); | ||
})} | ||
</NomberBoardContainer> | ||
</NumberGameLayout> | ||
); | ||
}; | ||
|
||
export default NumberGame; | ||
|
||
const NumberGameLayout = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
gap: 3rem; | ||
`; | ||
|
||
const NumberBoardTextStyle = styled.span` | ||
${theme.font.subHead} | ||
`; | ||
|
||
const NomberBoardContainer = styled.div` | ||
display: grid; | ||
grid-template-columns: repeat(3, 1fr); | ||
gap: 0.5rem; | ||
`; |
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.
이 부분에서
buttonState
와content
가 비슷한 역할을 하고 있는 것으로 보여서혹시 둘 다 작성하신 이유가 있을까요??