Skip to content
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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions week3/assignment/.gitignore
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?
8 changes: 8 additions & 0 deletions week3/assignment/README.md
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
13 changes: 13 additions & 0 deletions week3/assignment/index.html
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>
30 changes: 30 additions & 0 deletions week3/assignment/package.json
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"
}
}
1 change: 1 addition & 0 deletions week3/assignment/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions week3/assignment/src/App.jsx
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;
110 changes: 110 additions & 0 deletions week3/assignment/src/components/Header.jsx
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);
};
Comment on lines +6 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분에서 buttonStatecontent가 비슷한 역할을 하고 있는 것으로 보여서

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;
`;
32 changes: 32 additions & 0 deletions week3/assignment/src/components/NumberCard.jsx
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

Choose a reason for hiding this comment

The 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")};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visibility 속성을 사용해서 슷자카드를 조정할 수 있네요!! 🤩

`;

const NumberCardTextStyle = styled.span`
color: ${theme.color.white};
${theme.font.head}
`;
98 changes: 98 additions & 0 deletions week3/assignment/src/components/NumberGame.jsx
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));
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

숫자 직접 넣으셨는데,
유지보수 하기에도 그렇지만
level 에 따라 달라지는 부분 구현을 나중에 추가한다는 가정하에
state 로 관리하거나, 변수로 관리하면 좋을 것 같아요!!


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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleCardNumberChange 함수를 별도로 만들지 않고 setCardNumber((prev) => prev + 1)로 작성하면 더 직관적이지 않을까,, 생각합니다!


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;
`;
Loading