어빌리티 스톤 시뮬레이터
리액트로 요즘에 즐겨하는 로스트아크 라는 게임의 돌을 깎는 시뮬레이터를 만드려고 한다. 일단 구성은
1. side - 깎은 돌을 저장 했을때 로컬스토리지에 등록후 side에 리스트로 보여줄 예정
2. mainBox - 돌을 깎는 액션을 할 컴포넌트
3. bottom - 돌을 깎으며 누른 순서를 히스토리로 보여줄 예정
side에서 이미 깍은 돌 클릭시 bottom도 같이 보여줌
화면 구성
데이터 구조
채워지는 마름모가 세로로 3칸 가로로 10칸 이기때문에 3 * 10 의 2차원 배열을 만들어 그안에 체크여부와 성공여부의 값을 객체로 넣어줄 예정
let state = [
[ {result: "", checked: false }, {result: "", checked: false }...],//10개
[...],//위와 동일
[...]//위와 동일
]
컴포넌트 구조
MainBox
export default function MainBox(params) {
//3x10의 2차원 배열 선언 [[{},{},{}...],[{},{},{}...],[{},{},{}...]]
const [stone, setStone] = useState(Array.from(Array(3), () => new Array(10).fill({ result: "", checked: false })));
//성공확률 퍼센테이지 초기값 75%
const [percentage, setPercentage] = useState(75);
return (
<div className="MainBox">
{stone.map((ele, idx) => {
return (
<Engraving
className={idx === 2 ? "decrease" : "increase"} //className에 따라 box-shadow를 다르게 줌
key={idx}
Firstele={ele} //stone 2차원 배열의 1차 배열 [{},{},{}...]
Firstidx={idx} //인덱스 0,1,2
stone={stone}
setStone={setStone}
percentage={percentage}
setPercentage={setPercentage}
/>
);
})}
</div>
);
}
stone의 1차 배열만 꺼내서 3개의 Engraving컴포넌트를 만들어준다
Engraving
export default function Engraving({ className, Firstele, Firstidx, stone, setStone, percentage, setPercentage }) {
const [cnt, setCnt] = useState(0); //채워지는 칸을 위해 Engraving컴포넌트별 상태 만듬
const copiedStone = [...stone]; //stone을 직접 수정하면 안되기 때문에 복사본 만듬
const handleBtnOnclik = (index) => {
//index = 0,1,2
if (cnt < 10) {
//10칸 초과면 버튼 작동 안함
let random = Math.floor(Math.random() * 100); //확률 소숫점 내림
//랜덤으로 나온 숫자보다 percentage(초기 75%) 가 큰 경우 : 성공 / 아닌경우 그대로 fail
const isSuccess = percentage > random;
//성공이면
if (isSuccess) {
if (percentage > 25) {
//확률이 25% 초과일 경우 - 10%
setPercentage(percentage - 10);
}
} else {
//실패이면
if (percentage < 75) {
//확률이 75%미만 일경우 +10%
setPercentage(percentage + 10);
}
}
copiedStone[index][cnt] = Object.assign({}, { result: isSuccess ? "success" : "fail", checked: isSuccess });
//stone[index][cnt]의 객체{}를 { result: "success", checked: true }로 변경
setStone(copiedStone); //stone 상태 업데이트
setCnt(cnt + 1); //한칸 채워질때마다 다음칸으로 이동
}
};
return (
<>
<div>
{Firstidx === 0 ? "성공확률 " : Firstidx === 2 ? "균열확률 " : ""}
{Firstidx !== 1 ? percentage + "%" : ""}
</div>
<div className={"Engraving " + className}>
{/* className 으로 box-shadow 변경 */}
<div className="Engraving__img"></div>
<div className="Engraving__action">
<div className="Engraving__action__name"></div>
<div className="Engraving__action__box">
{Firstele.map((ele, idx) => {
return (
<div key={idx}>
<div
className={
"Engraving__action__box__square " +
(ele.result === "success" && Firstidx === 2 ? "success_red" : ele.result)
// 클래스 추가로 버튼 클릭시 마름모의 색을 바꿈
}
/>
</div>
);
})}
</div>
<div className="Engraving__action__result"></div>
</div>
{/* 모코코 망치버튼 */}
<div
className="Engraving__btn"
onClick={() => {
handleBtnOnclik(Firstidx);
}}
>
<img src={mokoko} alt="mokoko" />
</div>
</div>
</>
);
}
Engraving 컴포넌트 별 고유의 cnt state를 만들어 1칸씩 채워질때마다 다음칸으로 이동할 수 있게 만들어준다
ex)
순서 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 버튼 |
---|---|---|---|---|---|---|---|---|---|---|---|
index:0 | cnt:0 | cnt:1 | cnt:2 | cnt:3 | cnt:4 | cnt:5 | cnt:6 | cnt:7 | --- | --- | 버튼 |
index:1 | cnt:0 | cnt:1 | cnt:2 | --- | --- | --- | --- | --- | --- | --- | 버튼 |
index:2 | cnt:0 | cnt:1 | cnt:2 | cnt:3 | cnt:4 | cnt:5 | --- | --- | --- | --- | 버튼 |
버튼 클릭시 성공 여부를 위치에 맞게 배열안의 객체에 하나씩 넣어준다
copiedStone[index][cnt] = Object.assign({}, { result: isSuccess ? "success" : "fail", checked: isSuccess });
바뀐 값에 의해 추가되는 클래스가 달라지기 때문에 색상이 변경된다
{Firstele.map((ele, idx) => {
return (
<div key={idx}>
<div
className={
"Engraving__action__box__square " +
(ele.result === "success" && Firstidx === 2 ? "success_red" : ele.result)
// 클래스 추가로 버튼 클릭시 마름모의 색을 바꿈
}
/>
</div>
);
})}
ex)
success = blue
success_red = red
fail = black
65% 확률로 97돌 실패..
github
https://github.com/moonjh9392/stone-cutting
demo site
https://moonjh9392.github.io/stone-cutting/